我使用C语言读取一个 .png
图像文件,如果你不熟悉的是 PNG
编码格式,有用的整数值以 .png
文件的形式是4字节的大二烯整数。
我的电脑是一台小恩典机器,所以要从大恩典的 uint32_t
我从文件中读取的 fread()
到一个我的计算机能理解的小字型,我一直在使用我写的这个小函数。
#include <stdint.h>
uint32_t convertEndian(uint32_t val){
union{
uint32_t value;
char bytes[sizeof(uint32_t)];
}in,out;
in.value=val;
for(int i=0;i<sizeof(uint32_t);++i)
out.bytes[i]=in.bytes[sizeof(uint32_t)-1-i];
return out.value;
}
这在我的x86_64 UNIX环境下工作得很好。gcc
编译时没有错误或警告,即使在使用 -Wall
标志,但我很有信心地认为,我所依赖的是未定义的行为和类型逃逸,而这些行为和类型逃逸在其他系统上可能无法正常工作。
有没有一个标准的函数可以让我调用,把一个大字节的整数转换为本地机器可以理解的整数,或者如果没有,有没有其他更安全的方法来完成这种转换?
我看到OP的代码中没有真正的UB。
可移植性问题:是的。
"在其他系统上可能不那么好用的类型运行 "在OP的C代码中不是问题,但在其他语言中可能会造成麻烦。
然而用一个大的(PNG)endian来代替主机呢?
按地址提取字节(从MSByte的最低地址到LSByte的最高地址--"大 "endian),然后用移位后的字节形成结果。
就像这样。
uint32_t Endian_BigToHost32(uint32_t val) {
union {
uint32_t u32;
uint8_t u8[sizeof(uint32_t)]; // uint8_t insures a byte is 8 bits.
} x = { .u32 = val };
return
((uint32_t)x.u8[0] << 24) |
((uint32_t)x.u8[1] << 16) |
((uint32_t)x.u8[2] << 8) |
x.u8[3];
}
提示:许多库有一个特定的函数来有效地实现这个功能。例子 be32toh.
IMO从字节中读取所需的格式会更好,而不是明显的memcpy'ing一个uint32_t,然后在内部操作uint32_t。代码可能是这样的。
uint32_t read_be32(uint8_t *src) // must be unsigned input
{
return (src[0] * 0x1000000u) + (src[1] * 0x10000u) + (src[2] * 0x100u) + src[3];
}
这种代码很容易出错,所以要确保你是从高分SO用户😉那里得到的。 你可能会经常看到另一种建议 return (src[0] << 24) + (src[1] << 16) + (src[2] << 8) + src[3];
然而,这将导致未定义的行为,如果 src[0] >= 128
由于有符号的整数溢出,由于整数推广采取了不幸的规则。uint8_t
签署的 int
. 而且在使用16位int的系统上也会因为大位移而导致未定义的行为。
现代编译器 应 足够聪明的优化,这一点,比如说。由clang小安迪产生的汇编。 是。
read_be32: # @read_be32
mov eax, dword ptr [rdi]
bswap eax
ret
然而我看到gcc 10.1产生的代码要复杂得多 这似乎是一个令人惊讶的遗漏的优化bug
这个解决方案并不依赖于访问联合体中的非活动成员,而是依赖于无符号整数位移操作,它可以安全地从大二进制转换为小二进制。反之
#include <stdint.h>
uint32_t convertEndian32(uint32_t in){
return ((in&0xffu)<<24)|((in&0xff00u)<<8)|((in&0xff0000u)>>8)|((in&0xff000000u)>>24);
}
这段代码读取的是 uint32_t
从一个指针 uchar_t
在大恩迪安存储中,与你的架构的恩迪安性无关。 (代码就像读取256基数一样)
uint32_t read_bigend_int(uchar_t *p, int sz)
{
uint32_t result = 0;
while(sz--) {
result <<= 8; /* multiply by base */
result |= *p++; /* and add the next digit */
}
}
比如说,如果你打电话。
int main()
{
/* ... */
uchar_t buff[1024];
read(fd, buff, sizeof buff);
uint32_t value = read_bigend_int(buff + offset, sizeof value);
/* ... */
}