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

时间:2013-04-15 06:27:04

标签: c++ c portability endianness

背景

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

问题

无论您的代码运行的CPU是大端还是小端架构,您如何轻松编写小端值?(创意:可以使用标准宏ntohl()htonl()以某种方式“反向”使用?或者如果代码运行在一个小端或大端的CPU上并选择适当的代码路径,那么代码是否只是测试运行时?)

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

3 个答案:

答案 0 :(得分:18)

C已经提供了对主机字节序的抽象: number †或 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);

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

答案 1 :(得分:5)

以下是基于模板的版本:

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

}

答案 2 :(得分:2)

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

  

htonl函数将u_long从主机转换为TCP / IP网络字节   订单(这是大端)。

     

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

是的,runtime detecting endianness是一个非常理智的事情,基本上任何准备使用的宏/函数在某些时候都可以做。

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