检测字节序

时间:2012-01-23 21:35:37

标签: c c-preprocessor endianness compile-time

我目前正在尝试创建 C 源代码,无论目标系统的字节顺序如何,都能正确处理I / O.

我选择了“little endian”作为我的I / O约定,这意味着,对于大端CPU,我需要在写入或读取时转换数据。

转换不是问题。我面临的问题是检测字节顺序,最好是在编译时(因为CPU在执行过程中不会改变字节顺序......)。

到目前为止,我一直在使用它:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif

它被记录为GCC预定义宏,而Visual似乎也理解它。

但是,我收到报告称某些big_endian系统(PowerPC)检查失败。

所以,我正在寻找一个万无一失的解决方案,它可以确保无论编译器和目标系统如何都能正确检测到字节顺序。好吧,他们中的大多数至少......

[编辑]:提出的大多数解决方案都依赖于“运行时测试”。编译期间编译器有时可以正确评估这些测试,因此不会产生实际的运行时性能。

然而,用某种<<<<< if (0) { ... } else { ... }>>是不足够的。在当前的代码实现中,变量和函数声明依赖于big_endian检测。使用if语句无法更改这些内容。

嗯,显然,有一个后退计划,即重写代码......

我宁愿避免这种情况,但是,它看起来似乎正在减少希望......

[编辑2]:我通过深度修改代码测试了“运行时测试”。尽管他们正确地完成了工作,但这些测试也会影响性能。

我期待这样,因为测试具有可预测的输出,编译器可以消除坏分支。但不幸的是,它并不是一直有效。 MSVC是一个很好的编译器,并且成功地消除了坏的分支,但是GCC的结果是混合的,这取决于版本,测试类型,并且对64位比对32位的影响更大。

很奇怪。这也意味着编译器无法确保运行时测试。

编辑3 :这些天,我正在使用编译时常量联合,期望编译器将其解析为明确的是/否信号。 而且效果很好: https://godbolt.org/g/DAafKo

15 个答案:

答案 0 :(得分:14)

为什么不使用big-endian命令(多被认为是"network order")而不是查找编译时检查,而是使用大多数UNIX系统提供的htons/htonl/ntohs/ntohl函数和视窗。他们已经被定义为完成你想要做的工作。为什么重新发明轮子?

答案 1 :(得分:13)

如前所述,检测Big Endian的唯一“真正”方法是使用运行时测试。

但是,有时候,宏可能更受欢迎。

不幸的是,我没有找到一个“测试”来检测这种情况,而是检测它们的集合。

例如,GCC建议:__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__。但是,这仅适用于最新版本,早期版本(和其他编译器)将为此测试提供错误值“true”,因为NULL == NULL。所以你需要更完整的版本:defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)

好的,现在这适用于最新的GCC,但其他编译器呢?

您可以尝试通常在大端编译器上定义的__BIG_ENDIAN____BIG_ENDIAN_BIG_ENDIAN

这将改善检测。但是,如果您专门针对PowerPC平台,则可以添加一些测试以改进更多检测。试试_ARCH_PPC__PPC____PPCPPC__powerpc____powerpc甚至powerpc。将所有这些定义绑定在一起,您可以非常公平地检测大端系统,特别是powerpc,无论编译器及其版本如何。

因此,总而言之,没有“标准的预定义宏”可以保证在所有平台和编译器上检测大端CPU,但是有许多这样的预定义宏,它们共同给出了在大多数情况下正确检测大端的可能性很高。

答案 2 :(得分:11)

在C语言的编译时,除了信任预处理器#define之外,你做的不多,而且没有标准的解决方案,因为C标准不关心字节序。

但是,您可以添加一个在程序开始时在运行时完成的断言,以确保在编译时完成的假设为真:

inline int IsBigEndian()
{
    int i=1;
    return ! *((char *)&i);
}

/* ... */

#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif

(其中COMPILED_FOR_BIG_ENDIANCOMPILED_FOR_LITTLE_ENDIAN之前根据您的预处理程序字节顺序检查是#define d)

答案 3 :(得分:6)

尝试类似:

if(*(char *)(int[]){1}) {
    /* little endian code */
} else {
    /* big endian code */
}

并查看编译器是否在编译时解析它。如果没有,你可能会更好运与联盟做同样的事情。实际上我喜欢使用评估为0,1或1,0(分别)的联合来定义宏,这样我就可以做一些事情,比如访问buf[HI]buf[LO]

答案 4 :(得分:4)

尽管有编译器定义的宏,但我认为没有一种编译时方法可以检测到这种情况,因为确定架构的字节序包涉及分析它在内存中存储数据的方式。

这是一个能够做到这一点的功能:

bool IsLittleEndian () {

    int i=1;

    return (int)*((unsigned char *)&i)==1;

}

答案 5 :(得分:3)

正如其他人所指出的那样,在编译时没有一种可移植的方法来检查字节序。但是,一种选择是使用autoconf工具作为构建脚本的一部分来检测系统是big-endian还是little-endian,然后使用AC_C_BIGENDIAN宏来保存这些信息。从某种意义上说,这构建了一个程序,可以在运行时检测系统是big-endian还是little-endian,然后具有该程序输出信息,然后可以由主源代码静态使用。

希望这有帮助!

答案 6 :(得分:2)

这来自p。 Pointers in C中的45个:

#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1

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

int main(int argc, char* argv[])
{
   int value;
   value = endian();
   if (value == 1)
      printf("The machine is Little Endian\n");
   else
      printf("The machine is Big Endian\n");
   return 0;
}

答案 7 :(得分:1)

您无法在编译时检测到它可以在所有编译器中移植。也许你可以在运行时更改代码来实现它 - 这是可以实现的。

答案 8 :(得分:1)

使用预处理程序指令无法在C中可移植地检测字节顺序。

答案 9 :(得分:1)

套接字的ntohl函数可用于此目的。 Source

// Soner
#include <stdio.h>
#include <arpa/inet.h>


int main() {
    if (ntohl(0x12345678) == 0x12345678) {
        printf("big-endian\n");
    } else if (ntohl(0x12345678) == 0x78563412) {
        printf("little-endian\n");
    } else {
        printf("(stupid)-middle-endian\n");
    }
    return 0;
}

答案 10 :(得分:1)

我的 GCC 版本是 9.3.0,配置为支持 powerpc64 平台,我已经测试并验证它支持以下宏逻辑:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
......
#endif
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
.....
#endif

答案 11 :(得分:0)

我自由地重新格式化了引用的文本

  

自2017年7月18日起,我使用union { unsigned u; unsigned char c[4]; }

如果sizeof (unsigned) != 4,您的测试可能会失败。

最好使用

union { unsigned u; unsigned char c[sizeof (unsigned)]; }

答案 12 :(得分:0)

正如大多数人提到的那样,编译时间是最好的选择。假设您不进行交叉编译,而是使用cmake(当然,它也可以与其他工具(例如configure脚本一起使用),那么您可以使用预测试,它是已编译的.c或.cpp文件,它可以为您提供所运行处理器的实际验证字节序。

通过cmake,您可以使用TestBigEndian宏。它设置一个变量,然后可以将其传递到软件。像这样(未经测试):

TestBigEndian(IS_BIG_ENDIAN)
...
set(CFLAGS ${CFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C
set(CXXFLAGS ${CXXFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C++

然后在您的C / C ++代码中,您可以检查IS_BIG_ENDIAN是否定义:

#if IS_BIG_ENDIAN
    ...do big endian stuff here...
#else
    ...do little endian stuff here...
#endif

因此,这种测试的主要问题是交叉编译,因为您可能处于完全不同的CPU上,并且字节序不同。但是至少在编译其余代码时,它可以为您提供字节序,并且可以正常工作对于大多数项目。

答案 13 :(得分:-1)

我知道我参加这个派对已经很晚了,但这是我的看法。

int is_big_endian() {
    return 1 & *(uint16_t*)"01";
}

这是基于'0'为十进制48和'1' 49这一事实,因此'1'设置了LSB位,而'0'则没有。我可以制作'\x00''\x01',但我认为我的版本更具可读性。

答案 14 :(得分:-5)

#define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)