在 C++ 中处理字节顺序
Posted
技术标签:
【中文标题】在 C++ 中处理字节顺序【英文标题】:dealing with endianness in c++ 【发布时间】:2012-12-13 16:00:52 【问题描述】:我正在将系统从 python 转换为 c++。我需要能够在 c++ 中执行通常使用 Python 的 struct.unpack
执行的操作(将二进制字符串解释为数值)。对于整数值,我可以使用stdint.h
中的数据类型来实现(某种)工作:
struct.unpack("i", str) ==> *(int32_t*) str; //str is a char* containing the data
这适用于 little-endian 二进制字符串,但在 big-endian 二进制字符串上失败。基本上,我需要等效于在 struct.unpack 中使用 >
标记:
struct.unpack(">i", str) ==> ???
请注意,如果有更好的方法可以做到这一点,我会全力以赴。但是,我不能使用 c++11,也不能使用 Boost 以外的任何第三方库。我还需要能够解释浮点数和双精度数,如 struct.unpack(">f", str)
和 struct.unpack(">d", str)
,但我会在解决这个问题时解决这个问题。
注意我应该指出,在这种情况下,我的机器的字节序是无关紧要的。我知道我在代码中收到的比特流总是大端的,这就是为什么我需要一个始终涵盖大端情况的解决方案。 BoBTFish 在 cmets 中指出的文章似乎提供了一个解决方案。
【问题讨论】:
一个有趣的阅读:commandcenter.blogspot.co.uk/2012/04/byte-order-fallacy.html @BoBTFish 你是说我的代码“错误或被误导”,还是指出第 4 段中提供的解决方案? 不是。好吧,也许是第二个。只是指向一篇讨论这个我觉得很有趣的文章。我真的觉得没有资格提供正确的答案,但没有其他人在说什么。 【参考方案1】:对于 32 位和 16 位值:
这正是网络数据的问题,即大端。您可以使用 ntohl 将 32 位转换为主机顺序,在您的情况下为 little-endian。
ntohl() 函数将无符号整数 netlong 从网络字节顺序转换为 主机字节顺序。
int res = ntohl(*((int32_t) str)));
这也将处理您的主机是大端并且不会做任何事情的情况。
对于 64 位值
在 linux/BSD 上非标准可以看一下64 bit ntohl() in C++?,它指向htobe64
这些函数将整数值的字节编码从字节顺序转换为 当前 CPU(“主机”)使用来往小端和大端字节 顺序。
对于 Windows 试试:How do I convert between big-endian and little-endian values in C++?
它指向 _byteswap_uint64 以及 16 位和 32 位解决方案以及 gcc 特定的 __builtin_bswap(32/64) 调用。
其他尺寸
大多数系统没有非 16/32/64 位长的值。那时我可能会尝试将其存储为 64 位值,将其移位并进行翻译。我会写一些很好的测试。我怀疑这是一种不常见的情况,更多细节会有所帮助。
【讨论】:
2 件事:1) 请注意,您需要取消引用强制转换的指针才能编译:ntohl(*(int32_t*) str);
。 2) 我将如何处理 16 位和 32 位整数以外的值?我需要能够涵盖从 8 位到 64 位整数的所有内容,包括有符号和无符号的。
感谢修复,已添加。还尝试解决其他尺寸问题。【参考方案2】:
一次一个字节地解压字符串。
unsigned char *str;
unsigned int result;
result = *str++ << 24;
result |= *str++ << 16;
result |= *str++ << 8;
result |= *str++;
【讨论】:
这适用于 32 位的整数。根据 16 位和 64 位值的需要,使用更多或更少的 没有意义。我想如果str
是signed char*
会有意义。我已经删除了它们。
@Robᵩ 如果 str 已签名,& 0xff
将如何提供帮助?我意识到我正在以char*
的形式读取数据,但除非我将其转换为unsigned char*
,否则它不会正确转换。这样做有问题吗?
@ewok unsigned char *
绝对是二进制数据的最佳选择。不幸的是,您可能不得不在调用库函数时使用强制转换,这些库函数采用普通的 char *
,但这是值得的。【参考方案3】:
首先,你正在做的演员:
char *str = ...;
int32_t i = *(int32_t*)str;
由于严格的别名规则导致未定义的行为(除非str
被初始化为int32_t x; char *str = (char*)&x;
之类的东西)。实际上,强制转换可能会导致未对齐的读取,从而在某些平台上导致总线错误(崩溃)并在其他平台上降低性能。
相反,您应该这样做:
int32_t i;
std::memcpy(&i, c, sizeof(i));
有许多函数可以在主机的本机字节排序和主机独立排序之间交换字节:ntoh*()
、hton*()
,其中 *
什么都不是,l
或 s
用于不同的支持的类型。由于不同的主机可能有不同的字节顺序,如果您正在读取的数据在所有平台上使用一致的序列化形式,这可能就是您想要使用的。
ntoh(i);
您还可以在将 str
中的字节复制到整数之前手动移动它。
std::swap(str[0],str[3]);
std::swap(str[1],str[2]);
std::memcpy(&i,str,sizeof(i));
或者您可以使用移位和按位运算符手动操作整数的值。
std::memcpy(&i,str,sizeof(i));
i = (i&0xFFFF0000)>>16 | (i&0x0000FFFF)<<16;
i = (i&0xFF00FF00)>>8 | (i&0x00FF00FF)<<8;
【讨论】:
【参考方案4】:这属于比特旋转的领域。
for (i=0;i<sizeof(struct foo);i++) dst[i] = src[i ^ mask];
如果存储字节序和原生字节序不同,则其中 mask == (sizeof type -1)。
使用这种技术可以将结构转换为位掩码:
struct foo
byte a,b; // mask = 0,0
short e; // mask = 1,1
int g; // mask = 3,3,3,3,
double i; // mask = 7,7,7,7,7,7,7,7
s; // notice that all units must be aligned according their native size
同样,这些掩码可以用每个符号两位编码:(1<<n)-1
,这意味着在 64 位机器中,可以将 32 字节大小的结构的必要掩码编码为单个常量(1、2、4 和 8字节对齐)。
unsigned int mask = 0xffffaa50; // or zero if the endianness matches
for (i=0;i<16;i++)
dst[i]=src[i ^ ((1<<(mask & 3))-1]; mask>>=2;
【讨论】:
【参考方案5】:如果您收到的值是真正的字符串(char* 或 std::string)并且您知道它们的格式信息,sscanf() 和 atoi(),那么,真正的 atoi() 将是您的朋友。它们采用格式良好的字符串,并按照传入的格式进行转换(一种反向 printf)。
【讨论】:
他说网络顺序是大端的,如果它们以文本形式传递,这将是无关紧要的。以上是关于在 C++ 中处理字节顺序的主要内容,如果未能解决你的问题,请参考以下文章