处理c++中的endianness

dealing with endianness in c++

本文关键字:endianness 中的 c++ 处理      更新时间:2023-10-16

我正在把一个系统从python翻译成c++。我需要能够在c++中执行通常通过使用Python的struct.unpack(将二进制字符串解释为数值)执行的操作。对于整数值,我可以使用stdint.h:中的数据类型来实现这一点

struct.unpack("i", str) ==> *(int32_t*) str; //str is a char* containing the data

这种方法适用于小端二进制字符串,但在大端二进制字符串上失败。基本上,我需要一个等价于在struct.unpack:中使用>标签的东西

struct.unpack(">i", str) ==> ???

请注意,如果有更好的方法,我会洗耳恭听的。但是,我不能使用c++11,也不能使用除Boost之外的任何第三方库。我还需要能够解释float和doubles,如struct.unpack(">f", str)struct.unpack(">d", str),但当我解决这个问题时,我会做到这一点。

注意我应该指出,在这种情况下,我的机器的字节序无关紧要。我知道我在代码中收到的比特流将始终是big-endian,这就是为什么我需要一个始终涵盖big-endiancase的解决方案。BoBTFish在评论中指出的这篇文章似乎提供了一个解决方案。

对于32位和16位值:

这正是网络数据的问题,它是big-endian。您可以使用ntohl将32位转换为主机顺序,在您的情况下是小端序。

ntohl()函数将无符号整数netlong从网络字节顺序转换为主机字节顺序。

int res = ntohl(*((int32_t) str)));

这也将处理您的主机是big-endian并且不会执行任何操作的情况。

对于64位值

在linux/BSD上,你可以看看C++中的64位ntohl()?,指向htobe64

这些函数将整数值的字节编码从当前CPU("主机")使用、往返于小端字节和大端字节顺序

对于windows,请尝试:如何在C++中转换大端序和小端序值?

它指向_byteswap_uint64,以及一个16和32位解决方案和一个特定于gcc的__builtin_bswap(32/64)调用。

其他尺寸

大多数系统没有16/32/64位以外的值。在这一点上,我可能会尝试将其存储在64位值中,对其进行移位,然后它们进行翻译。我会写一些好的测试。我怀疑这是一种罕见的情况,更多的细节会有所帮助。

一次将字符串解包一个字节。

unsigned char *str;
unsigned int result;
result =  *str++ << 24;
result |= *str++ << 16;
result |= *str++ << 8;
result |= *str++;

首先,您正在进行的演员阵容:

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*(),其中*为空,ls用于支持的不同类型。由于不同的主机可能具有不同的字节顺序,因此如果您正在读取的数据在所有平台上使用一致的序列化形式,则这可能是您想要使用的。

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;

这属于位处理领域。

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;
}

如果你收到的值真的是字符串(char*或std::string),并且你知道它们的格式信息,sscan()和atoi(),那么,真的ato()将是你的朋友。它们采用格式良好的字符串,并根据传入的格式对其进行转换(有点像反向printf)。