你如何编写(便携式)反向网络字节顺序?

背景

在设计二进制文件格式时,通常建议以网络字节顺序写入整数。 为此,有像htonhl()这样的宏。 但对于像WAV这样的格式,实际上使用的是小端格式。

无论你的代码运行的CPU是大端还是小端架构,你如何便携地编写小端值? (想法:标准宏ntohl()htonl()可以以某种方式“反向”使用吗?或者如果代码只是运行在一个小端或大端的CPU上并选择适当的代码路径,那么它应该只测试运行时吗?)

所以问题不在于文件格式,文件格式只是一个例子。 它可以是任何类型的序列化,其中需要“线上”的小端,例如(异端)网络协议。

C已经提供了对主机字节序的抽象: 数字 †或int †。

以给定的字节顺序生成输出可以通过不尝试聪明来完成:只需将数字解释为数字并使用位移来提取每个字节:

 uint32_t value; uint8_t lolo = (value >> 0) & 0xFF; uint8_t lohi = (value >> 8) & 0xFF; uint8_t hilo = (value >> 16) & 0xFF; uint8_t hihi = (value >> 24) & 0xFF; 

然后你只需按照你想要的顺序写字节。

当您将带有某些字节序的字节序列作为输入时,您可以通过再次使用位操作构造数字来在主机的字节序中重构它们:

 uint32_t value = (hihi << 24) | (hilo << 16) | (lohi << 8) | (lolo << 0); 

†只有作为字节序列的数字表示具有字节顺序; 数字(即数量)没有。

这是一个基于模板的版本:

 #include  #include  enum endianness_t { BIG, // 0x44332211 => 0x44 0x33 0x22 0x11 LITTLE, // 0x44332211 => 0x11 0x22 0x33 0x44 UNKNOWN }; const uint32_t test_value = 0x44332211; const bool is_little_endian = (((char *)&test_value)[0] == 0x11) && (((char *)&test_value)[1] == 0x22); const bool is_big_endian = (((char *)&test_value)[0] == 0x44) && (((char *)&test_value)[1] == 0x33); const endianness_t endianness = is_big_endian ? BIG: (is_little_endian ? LITTLE : UNKNOWN); template  T identity(T v){ return v; } // 16 bits values ------ uint16_t swap_(uint16_t v){ return ((v & 0xFF) << 8) | ((v & 0xFF00) >> 8); } // 32 bits values ------ uint32_t swap_(uint32_t v){ return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v & 0xFF0000) >> 8) | ((v & 0xFF000000) >> 24); } template  struct en_swap{ static T conv(T v){ return swap_(v); } }; template  struct en_swap{ static T conv(T v){ return v; } }; template  struct en_swap { static T conv(T v){ return v; } }; template  T to_big(T v) { switch (endianness){ case LITTLE : return en_swap::conv(v); case BIG : return en_swap::conv(v); } } template  T to_little(T v) { switch (endianness){ case LITTLE : return en_swap::conv(v); case BIG : return en_swap::conv(v); } } int main(){ using namespace std; uint32_t x = 0x0ABCDEF0; uint32_t y = to_big(x); uint32_t z = to_little(x); cout << hex << setw(8) << setfill('0') << x << " " << y << " " << setw(8) << setfill('0') << z << endl; } 

实际上,MSDN函数ntohl ()和htonl () 彼此相反的:

htonl函数将u_long从主机转换为TCP / IP网络字节顺序(这是big-endian)。

ntohl函数将u_long从TCP / IP网络顺序转换为主机字节顺序(在Intel处理器上为little-endian)。

是的, 运行时检测字节序是一个非常理智的事情,基本上任何现成的宏/函数在某些时候都可以做。

如果你想自己做一些大端的转换,请参阅@ R-Martinho-Fernandes的回答。