在C ++程序中以编程方式检测字节顺序

是否有一种编程方式来检测您是否处于big-endian或little-endian架构? 我需要能够编写将在Intel或PPC系统上执行的代码并使用完全相同的代码(即没有条件编译)。

我不喜欢基于类型惩罚的方法 – 它经常会被编译器警告。 这正是工会的意义所在!

bool is_big_endian(void) { union { uint32_t i; char c[4]; } bint = {0x01020304}; return bint.c[0] == 1; } 

该原则相当于其他人建议的类型情况,但这更清楚 – 根据C99,保证是正确的。 与直接指针转换相比,gcc更喜欢这个。

这比在编译时修复字节顺序要好得多 – 对于支持多体系结构的操作系统(例如Mac OS x上的胖二进制文件),这对于ppc / i386都有效,否则它很容易搞乱。 。

您可以通过设置int和屏蔽掉位来实现,但最简单的方法可能就是使用内置的网络字节转换操作(因为网络字节顺序总是大端)。

 if ( htonl(47) == 47 ) { // Big endian } else { // Little endian. } 

比特摆弄可能会更快,但这种方式很简单,直截了当,而且非常不可能搞砸。

请看这篇文章 :

以下是一些用于确定机器类型的代码

 int num = 1; if(*(char *)&num == 1) { printf("\nLittle-Endian\n"); } else { printf("Big-Endian\n"); } 

这通常在编译时完成(特别是出于性能原因),使用编译器提供的头文件或创建自己的头文件。 在linux上你有头文件“/usr/include/endian.h”

如果您可以访问C ++ 20编译器(如GCC 8+或Clang 7+),则可以使用std::endian

 #include  if constexpr (std::endian::native == std::endian::big) { // Big endian system } else if constexpr (std::endian::native == std::endian::little) { // Little endian system } else { // Something else } 

嗯……让我感到惊讶的是,没有人意识到编译器会简单地优化测试,并将固定结果作为返回值。 这将呈现上面的所有代码示例,实际上无用。 唯一可以返回的是编译时的字节序! 是的,我测试了上面的所有例子。 这是MSVC 9.0(Visual Studio 2008)的一个示例。

纯C代码

 int32 DNA_GetEndianness(void) { union { uint8 c[4]; uint32 i; } u; ui = 0x01020304; if (0x04 == uc[0]) return DNA_ENDIAN_LITTLE; else if (0x01 == uc[0]) return DNA_ENDIAN_BIG; else return DNA_ENDIAN_UNKNOWN; } 

拆卸

 PUBLIC _DNA_GetEndianness ; Function compile flags: /Ogtpy ; File c:\development\dna\source\libraries\dna\endian.c ; COMDAT _DNA_GetEndianness _TEXT SEGMENT _DNA_GetEndianness PROC ; COMDAT ; 11 : union ; 12 : { ; 13 : uint8 c[4]; ; 14 : uint32 i; ; 15 : } u; ; 16 : ; 17 : ui = 1; ; 18 : ; 19 : if (1 == uc[0]) ; 20 : return DNA_ENDIAN_LITTLE; mov eax, 1 ; 21 : else if (1 == uc[3]) ; 22 : return DNA_ENDIAN_BIG; ; 23 : else ; 24 : return DNA_ENDIAN_UNKNOWN; ; 25 : } ret _DNA_GetEndianness ENDP END 

也许可以关闭这个函数的任何编译时优化,但我不知道。 否则,可能可以在assembly中对其进行硬编码,尽管这不是便携式的。 即便如此,即使这样也可能会得到优化。 它让我觉得我需要一些非常糟糕的汇编程序,为所有现有的CPU /指令集实现相同的代码,而且……没关系。

此外,有人在此表示字节序在运行期间不会改变。 错误。 那里有双端机器。 他们的字节顺序可能因执行而异。 此外,不仅有Little Endian和Big Endian,还有其他的endianness(简而言之)。

我讨厌并喜欢同时编码……

声明一个int变量:

 int variable = 0xFF; 

现在使用char *指针指向它的各个部分并检查这些部分中的内容。

 char* startPart = reinterpret_cast( &variable ); char* endPart = reinterpret_cast( &variable ) + sizeof( int ) - 1; 

根据哪一个指向0xFF字节,您现在可以检测字节顺序。 这需要sizeof(int)> sizeof(char),但对于讨论的平台来说肯定是正确的。

我很惊讶没有人提到预处理器默认定义的宏。 虽然这些将根据您的平台而有所不同; 它们比编写自己的endian-check要清晰得多。

例如; 如果我们看一下GCC定义的内置宏(在X86-64机器上):

 :| gcc -dM -E -xc - |grep -i endian #define __LITTLE_ENDIAN__ 1 

在PPC机器上,我得到:

 :| gcc -dM -E -xc - |grep -i endian #define __BIG_ENDIAN__ 1 #define _BIG_ENDIAN 1 

:| gcc -dM -E -xc - magic打印出所有内置宏)。

有关更多详细信息,您可能需要查看此代码项目文章有关Endianness的基本概念 :

如何在运行时动态测试Endian类型?

如计算机动画常见问题解答中所述,您可以使用以下函数来查看您的代码是在Little-还是Big-Endian系统上运行:折叠

 #define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1 
 int TestByteOrder() { short int word = 0x0001; char *byte = (char *) &word; return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN); } 

此代码将值0001h分配给16位整数。 然后指定char指针指向整数值的第一个(最低有效)字节。 如果整数的第一个字节是0x01h,那么系统是Little-Endian(0x01h是最低或最不重要的地址)。 如果是0x00h,那么系统是Big-Endian。

如上所述,使用联合技巧。

上面建议的问题很少,最值得注意的是,对于大多数架构来说,未对齐的内存访问是非常慢的,并且一些编译器甚至根本不会识别这样的常量谓词,除非字对齐。

因为光端测试很无聊,所以这里有(模板)函数,它将根据你的规范翻转任意整数的输入/输出,而不管主机架构如何。

 #include  #define BIG_ENDIAN 1 #define LITTLE_ENDIAN 0 template  T endian(T w, uint32_t endian) { // this gets optimized out into if (endian == host_endian) return w; union { uint64_t quad; uint32_t islittle; } t; t.quad = 1; if (t.islittle ^ endian) return w; T r = 0; // decent compilers will unroll this (gcc) // or even convert straight into single bswap (clang) for (int i = 0; i < sizeof(r); i++) { r <<= 8; r |= w & 0xff; w >>= 8; } return r; }; 

用法:

要从给定的endian转换为host,请使用:

host = endian(source, endian_of_source)

要从host endian转换为给定的endian,请使用:

output = endian(hostsource, endian_you_want_to_output)

结果代码与在clang上编写手工组件一样快,在gcc上它的速度稍慢(展开&,<<,>>,对于每个字节)但仍然不错。

除非您使用已移植到PPC和Intel处理器的框架,否则您将不得不进行条件编译,因为PPC和Intel平台具有完全不同的硬件架构,流水线,总线等。这使得汇编代码完全不同他们俩。

至于查找字节序,请执行以下操作:

 short temp = 0x1234; char* tempChar = (char*)&temp; 

您将获得tempChar为0x12或0x34,从中您将知道字节序。

C ++的方法是使用boost ,其中预处理器检查和强制转换被分隔在经过深度测试的库中。

Predef库(boost / predef.h)识别四种不同的字节序 。

计划将Endian库提交给C ++标准,并支持对字节序敏感数据的各种操作。

如上面的答案所述,Endianness将是c ++ 20的一部分。

我会做这样的事情:

 bool isBigEndian() { static unsigned long x(1); static bool result(reinterpret_cast(&x)[0] == 0); return result; } 

沿着这些方向,您将获得一个只进行一次计算的时间效率函数。

 bool isBigEndian() { static const uint16_t m_endianCheck(0x00ff); return ( *((uint8_t*)&m_endianCheck) == 0x0); } 

编译时,非宏,C ++ 11 constexpr解决方案:

 union { uint16_t s; unsigned char c[2]; } constexpr static d {1}; constexpr bool is_little_endian() { return dc[0] == 1; } 
 union { int i; char c[sizeof(int)]; } x; xi = 1; if(xc[0] == 1) printf("little-endian\n"); else printf("big-endian\n"); 

这是另一种解决方案。 与Andrew Hare的解决方案类似。

未经测试,但在我看来,这应该工作? 因为它在小端上是0x01,在大端上是0x00?

 bool runtimeIsLittleEndian(void) { volatile uint16_t i=1; return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big } 

你也可以通过预处理器使用类似boost头文件的东西来做到这一点,可以找到boost endian

 int i=1; char *c=(char*)&i; bool littleendian=c; 

这个怎么样?

 #include  int main() { unsigned int n = 1; char *p = 0; p = (char*)&n; if (*p == 1) std::printf("Little Endian\n"); else if (*(p + sizeof(int) - 1) == 1) std::printf("Big Endian\n"); else std::printf("What the crap?\n"); return 0; } 

除非endian标头仅为GCC,否则它提供了您可以使用的宏。

 #include "endian.h" ... if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... } else if (__BYTE_ORDER == __BIG_ENDIAN) { ... } else { throw std::runtime_error("Sorry, this version does not support PDP Endian!"); ... 

如果您不想要条件编译,您可以只编写字节序独立代码。 这是一个例子(摘自Rob Pike ):

以endian独立方式读取存储在little-endian磁盘上的整数:

 i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24); 

同样的代码,试图考虑机器的字节顺序:

 i = *((int*)data); #ifdef BIG_ENDIAN /* swap the bytes */ i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0); #endif 

请参阅字节顺序 – C级代码图。

 // assuming target architecture is 32-bit = 4-Bytes enum ENDIANESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE }; ENDIANESS CheckArchEndianalityV1( void ) { int Endian = 0x00000001; // assuming target architecture is 32-bit // as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least Significant Byte) = 0x01 // casting down to a single byte value LSB discarding higher bytes return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN; } 

这是另一个C版本。 它定义了一个名为wicked_cast()的宏,用于通过C99联合字面值和非标准__typeof__运算符进行内联类型惩罚。

 #include  #if UCHAR_MAX == UINT_MAX #error endianness irrelevant as sizeof(int) == 1 #endif #define wicked_cast(TYPE, VALUE) \ (((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest) _Bool is_little_endian(void) { return wicked_cast(unsigned char, 1u); } 

如果整数是单字节值,则字节顺序没有意义,并且将生成编译时错误。

C编译器(至少我认识的每个人)使用endianness的方式必须在编译时决定。 即使对于biendian处理器(如ARM och MIPS),您也必须在编译时选择字节序。 此外,字节序在可执行文件(例如ELF)的所有常见文件格式中定义。 虽然可以制作二进制blob的biandian代码(对某些ARM服务器漏洞可能?),但可能必须在汇编中完成。

我正在阅读教科书: 计算机系统:程序员的观点 ,并且有一个问题需要通过C程序来确定哪个字节序。

我使用指针的function来执行以下操作:

 #include  int main(void){ int i=1; unsigned char* ii = &i; printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big")); return 0; } 

因为int占用4个字节,而char只占用1个字节。 我们可以使用char指针指向值为1的int 。因此,如果计算机是小端,则char指针指向的char值为1,否则,其值应为0。

正如Coriiander所指出的,这里的大多数(如果不是全部)代码将在编译时被优化掉,因此生成的二进制文件不会在运行时检查“endianness”。

已经观察到给定的可执行文件不应该以两个不同的字节顺序运行,但我不知道是否总是这样,并且在编译时检查它似乎是一个hack。 所以我编写了这个函数:

 #include  int* _BE = 0; int is_big_endian() { if (_BE == 0) { uint16_t* teste = (uint16_t*)malloc(4); *teste = (*teste & 0x01FE) | 0x0100; uint8_t teste2 = ((uint8_t*) teste)[0]; free(teste); _BE = (int*)malloc(sizeof(int)); *_BE = (0x01 == teste2); } return *_BE; } 

MinGW无法优化此代码,即使它确实优化了其他代码。 我相信这是因为我保留了在较小字节内存上的“随机”值(至少有7位),因此编译器无法知道该随机值是什么并且它不会优化function了。

我还编写了函数,以便只执行一次检查,并为下一次测试存储返回值。