11

背景

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

问题

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

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

4

3 回答 3

20

警告:这仅适用于无符号整数,因为有符号右移是实现定义的并且可能导致漏洞(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);

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

于 2013-04-15T06:32:21.667 回答
6

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

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

}
于 2013-04-15T07:03:08.370 回答
2

实际上,MSDN 中的函数ntohl () 和htonl ()是互逆的:

htonl 函数将 u_long 从主机转换为 TCP/IP 网络字节顺序(大端序)。

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

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

如果您想自己进行小端大端转换,请参阅@R-Martinho-Fernandes 的回答。

于 2013-04-15T06:38:33.113 回答