C++:使用指针将无符号字符转换为无符号整数,而不使用位移

4
假设我有一个C风格的unsigned char类型的数组:
unsigned char * c = (unsigned char *) malloc(5000 * sizeof(unsigned char));
for(int i = 0; i < 5000; i++) 
    c[i] = (unsigned char) ((i >> (i%4 * 8)) & 0xFF);

假设我有一个指针偏移量,可以指向一个以4字节整数开头的位置:
// pseudo code
unsigned int i = c + 10; // 10 = pointer offset, let's say. 

如果我想将i赋值为正确的数字,我可以这样做:

unsigned int i = (*(c+10) << 24) + (*(c+11) << 16) + (*(c+12) << 8) + (*(c+13));

但我难道不能使用转换来做到这一点吗?

// pseudo code -- I haven't gotten this to work yet: 

int i = (unsigned int) (*((void *)(c+10));

// or maybe
int i = *((unsigned int*)((void *)(c+10)));

简而言之,在C语言风格的字节数组中,将四个字节转换为一个无符号整数的最干净、最有效的方法是什么?

2
unsigned char * c 不是一个数组,而是一个指针。 - David Conrad
@bordeo -- 很多人常常会创建一个指向空地址的指针并将其视为数组。这种区别非常重要,当有人指出时不要感到冒犯。 - Pete Becker
请查看什么是严格别名规则?以获取所有细节。 - Shafik Yaghmour
3个回答

7
正确的方法是使用memcpy:
unsigned int i;
std::memcpy(&i, c + offset, sizeof(unsigned int));

在支持非对齐变量访问(如x86-64)的体系结构上,这将被优化为简单的指针解引用,但在不支持非对齐访问的系统上(如ARM),它将执行适当的操作以获得值。例如,请参见:https://gcc.godbolt.org/z/l5Px4G。在x86和arm之间切换编译器并查看指令差异。
请注意,如果您从某些外部源获取数据,则要考虑大小端的概念。您可能需要翻转整数的字节才能使值有意义。

1
为什么要使用特定于平台的代码,而存在同样高效的跨平台选项呢? - David Schwartz
3
我的方法有哪些是特定于平台的? - Paul Belanger
1
i的值取决于平台的字节序。 - David Schwartz
1
@DavidSchwartz 我们在你的回答中已经讨论过这个问题了。如果不知道缓冲区最初是如何填充的,就无法回答它是否依赖于此。如果缓冲区是由同一应用程序创建的,则不会依赖于此。 - SergeyA
1
@PaulBelanger 问问题的人受到了XY问题的阻碍。告诉他们不使用移位操作的愿望是愚蠢的是可以的。 - eerorika
显示剩余9条评论

3

但是,使用强制转换是否就可以实现这个功能呢?

不,没有任何一种强制转换可以保证成功。


请注意,整数有许多表示方法。如何将字节数组转换为整数对象取决于整数在数组中的表示方式。例如,如果将整数转换为字节数组并通过网络发送,则无法知道接收计算机是否使用相同的表示方式。

一个考虑因素是如何表示负数。幸运的是,补码是一种如此普遍的表示方式,我们通常可以忽略这一点。但在您的情况下,这更不重要,因为您正在转换无符号整数。

更相关的考虑是字节序。

如果您知道数组与执行程序的CPU使用的表示方式相同,则可以使用std::memcpy复制字节:

unsigned int i;
static_assert(sizeof i == 4);
std::memcpy(&i, c + 10, sizeof i);

无论CPU使用的字节序如何,只要源数据表示相同,此方法都可以正确工作。
如果字节数组的表示形式是大端,则您的建议(*(c+10) << 24) + ...是正确的(或者看起来是正确的,我没有仔细检查)。如果数组是小端或其他字节序,则该建议是错误的。
当通过网络接收数据时,这种方法非常有用,因为它不依赖于表示形式与执行CPU相同。

assert(sizeof i == 4); 是用来做什么的? - NathanOliver
@NathanOliver 好的,OP 表示数组包含一个 4 字节整数,而 int 并不能保证是 4 字节。另一方面,memcpy 依赖于字节序与 CPU 匹配,因此假设大小与本机匹配也不是太牵强。 - eerorika
好的。那很有道理。我忘记OP说数组中的“int”是4个字节。 - NathanOliver
@FrançoisAndrieux 嗯,CHAR_BIT不能保证为8,所以sizeof(int32_t)不能保证为4 :) - eerorika
@user2079303 这是正确的,但是 int32_t 可能不会被定义。但是假设 CHAR_BIT 是 16 并且确实有一个 int32_t,那么 int32_t 将读取 2 个 16 位字节。假设这是跨平台数据传输情况(否则,讨论就没有意义),那么这将是可以接受的。或者说,至少比当前的 int 解决方案更好。关键是,要读取确切的 32 位数据。编辑:实际上,在这种情况下,您的解决方案将失败,因为您将读取 CHAR_BIT * 4 位,这不是预期的结果。 - François Andrieux
显示剩余7条评论

3
不,不应该这样做。给一个指向已分配对象的指针添加一个不是对象大小的倍数的偏移量可能会导致平台无法解引用的指针。它根本不是unsigned int的指针。
在某些平台上,性能将非常糟糕。在某些平台上,代码将发生错误。
无论如何,移位和加法非常清晰易懂。强制转换更加令人困惑,需要了解平台的字节顺序。因此,你并没有使事情变得更好、更简单或更清晰。

1
这只是一个观点。什么是答案? - Chris
不同意。这完全取决于字符缓冲区是如何填充的。而且在平台(x86_64)上,性能将与对齐数据完全相同。 - SergeyA
1
就标准 c++ 而言,这个答案是正确的。任何可能在某些平台上工作的担忧都是在赌注未定义的行为或平台/编译器特定的保证。编辑:此评论是指第一段。最后一段是一个意见。 - François Andrieux
@SergeyA 我不明白你的意思。缓冲区如何填充会影响什么? - David Schwartz
2
让我们在聊天中继续此讨论 - David Schwartz
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接