背景
设计二进制文件格式时,通常建议以网络字节顺序写入整数。为此,存在诸如htonhl()
的宏。但是对于WAV之类的格式,实际上使用了小尾数格式。
问题
[[无论代码运行的CPU是大端还是小端架构,您如何都可移植地写入小端值?(想法:可以使用标准宏ntohl()
和htonl()
“相反”,还是代码应该只是测试运行时(如果运行在小端或大端CPU上并选择适当的代码路径?)
因此,问题实际上与文件格式无关,文件格式仅是示例。它可以是任何需要“在线上”很少字节序的序列化,例如(异端)网络协议。
警告:这仅适用于无符号整数,因为有符号的右移是在实现中定义的,并且可能导致漏洞(https://stackoverflow.com/a/7522498/395029)
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);
†只有数字表示为字节序列时才具有字节序;数字(即数量)不是。
#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;
}
are >>彼此相反: htonl函数将u_long从主机转换为TCP / IP网络字节 顺序(大端)。
ntohl函数将u_long从TCP / IP网络顺序转换为主机 字节顺序(在Intel处理器上为little-endian)。
是的,runtime detecting endianness是一件非常明智的事情,基本上是任何即时可用的宏/函数在任何时候都将要做的事情。
并且,如果您想自己进行小的字节序转换,请参阅@ R-Martinho-Fernandes的回答。