如何编写(可移植的)反向网络字节顺序

How do you write (portably) reverse network byte order?

本文关键字:网络字节顺序 何编写 可移植      更新时间:2023-10-16

背景

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

问题

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

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

警告:这只适用于无符号整数,因为有符号右移是实现定义的,可能会导致漏洞(https://stackoverflow.com/a/7522498/395029)

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 <iostream>
#include <iomanip>
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 <typename T>
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 <typename T, endianness_t HOST, endianness_t REMOTE>
 struct en_swap{
  static T conv(T v){
    return swap_(v);
  }
};
template <typename T>
struct en_swap<T, BIG, BIG>{
  static T conv(T v){
    return v;
  }
};
template <typename T>
struct en_swap<T, LITTLE, LITTLE> {
  static T conv(T v){
    return v;
  }
};
template <typename T>
T to_big(T v) {
  switch (endianness){
  case LITTLE :
    return en_swap<T,LITTLE,BIG>::conv(v);
  case BIG :
    return en_swap<T,BIG,BIG>::conv(v);
  }
}
template <typename T>
T to_little(T v) {
   switch (endianness){
   case LITTLE :
     return en_swap<T,LITTLE,LITTLE>::conv(v);
   case BIG :
     return en_swap<T,BIG,LITTLE>::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网络顺序转换为主机字节顺序(在英特尔处理器上是小端序(。

是的,运行时检测端序是一件非常明智的事情,基本上是任何现成的宏/函数在某个时候都会做的事情。

若你们想自己进行小-大端转换,请参阅@R-Martinho-Fernandes的回答。