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

C++ 如何写(可移植的)反向网络字节顺序?,c++,c,portability,endianness,C++,C,Portability,Endianness,背景 在设计二进制文件格式时,通常建议以网络字节顺序写入整数。为此,有一些宏,如htonhl()。但是对于WAV这样的格式,实际上使用的是little endian格式 问题: 无论代码运行的CPU是big-endian还是little-endian体系结构,如何可移植地编写little-endian值?(想法:标准宏ntohl()和htonl()是否可以“反向”使用不知何故?或者,如果代码运行在小型或大型CPU上,是否应该测试运行时并选择适当的代码路径?) 所以问题不是关于文件格式,文件格式只

背景

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

问题:

无论代码运行的CPU是big-endian还是little-endian体系结构,如何可移植地编写little-endian值?(想法:标准宏
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);

uint32\u t value=(hihi事实上,MSDN函数()和()是彼此的倒数:

htonl函数将u_长度从主机转换为TCP/IP网络字节 顺序(即big-endian)

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

是的,这是一件非常明智的事情,基本上是任何随时可用的宏/函数在某个时候都会做的事情


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

这里有一个基于模板的版本:

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

}
#包括
#包括
枚举终止性{
大,//0x44332211=>0x44 0x33 0x22 0x11
小,//0x44332211=>0x11 0x22 0x33 0x44
不为人知
};
常数32测试值=0x44332211;
常量布尔是((字符*)和测试值)[0]==0x11)和((字符*)和测试值)[1]==0x22);
常量布尔是&((字符*)和测试值)[0]==0x44]&((字符*)和测试值)[1]==0x33);
常数端性\u t端性=
大吗?大:
(小丁安?小丁:未知);
模板
T身份(tv){
返回v;
}
//16位值------
uint16\U t交换(uint16\U t v){
返回((v&0xFF)>8);
}
//32位值------
uint32\U t交换(uint32\U t v){
返回((v&0xFF)8)|((v&0xFF000000)>>24);
}
模板
结构交换{
静态T转换(T v){
返回交换(v);;
}
};
模板
结构交换{
静态T转换(T v){
返回v;
}
};
模板
结构交换{
静态T转换(T v){
返回v;
}
};
模板
T至_大(T v){
开关(端度){
小案例:
返回en_swap::conv(v);
案例大:
返回en_swap::conv(v);
}
}
模板
T至_little(T v){
开关(端度){
小案例:
返回en_swap::conv(v);
案例大:
返回en_swap::conv(v);
}
}
int main(){
使用名称空间std;
uint32_t x=0x0ABCDEF0;
uint32_t y=到_big(x);
uint32_t z=to_little(x);

你有三个问题,但只有两个突出。@user2096041,重新排列,是否更清晰?为了找到主机系统的终结点,你可以点击以下链接:
hihi
lolo
,这些是我见过的最好的名字。你说的数量是什么意思?@AmigableClarkKant数字的抽象概念,而不是它的r好的,也许你应该链接到某种C++的RF来代替数字。总之,它对序列化和循环性有什么帮助?我理解(并且赞成其余的答案)。它基本上意味着C++使主机的连接性不相关,不需要检测它,只需要按期望的顺序写出字节。<代码> int <代码> s,<代码> uint 32 t t <代码>等,是数字(它们没有字节数)。。我不同意检测endian是一件非常明智的事情。通常情况下,你不需要知道。除非你在编写性能关键的代码,否则编写endian独立的代码是一个更好的主意。@jamesdlin在编写跨平台进程时,相同的代码可以在小endian或大endian硬件上运行。当进程通信时通过网络或文件连接,您希望它们使用相同的语言。代码可以在运行时检查endian,并相应地调整进程输出/输入。这没有意义。如果您正在编写正确的endian不可知代码,那么您的代码仍然是跨平台的,不需要执行任何运行时(或编译时)操作检查。例如,如果您这样做:
fputc((x>>24)&0xFF,fp)
其中
x
是32位整数,则无论平台如何,您都将始终写出最高有效字节。请参阅R.Martinho Fernandes的回答。