【问题标题】:Detecting Endianness检测字节顺序
【发布时间】:2026-02-02 20:45:01
【问题描述】:

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

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

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

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

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

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

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

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

[Edit] : 大多数提出的解决方案都依赖于“运行时测试”。这些测试有时可能会在编译期间由编译器正确评估,因此不会消耗真正的运行时性能。

但是,使用某种 if (0) { ... } else { ... } >> 进行分支是不够的。在当前的代码实现中,变量和函数声明依赖于big_endian检测。这些不能用 if 语句改变。

嗯,很明显,有后备计划,就是重写代码……

我宁愿避免这种情况,但是,好吧,看起来希望越来越渺茫......

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

我期待,由于测试具有可预测的输出,编译器可以消除不良分支。但不幸的是,它并不总是有效。 MSVC 是很好的编译器,并且可以成功地消除不良分支,但 GCC 的结果参差不齐,这取决于版本、测试类型,并且对 64 位的影响比对 32 位的影响更大。

这很奇怪。这也意味着运行时测试不能保证被编译器处理。

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

【问题讨论】:

  • @BoPersson - 这不是编译时检测
  • 运行时间是您最好的选择,但编译时间包含在以下答案中:1. *.com/a/1001373/1094175 2. *.com/a/2100385/1094175
  • 一些 CPU 实际上可以对不同的可执行文件有不同的字节序。 en.wikipedia.org/wiki/Endianness#Bi-endian_hardware
  • @Cyan ,除了你提到的那些,没有一个。因此,要么编译一个检测字节序的小程序,并将结果输入你的构建系统,以便它定义一个预处理器宏,要么编写代码,使其独立于主机字节序。
  • 基于预处理器的测试可能失败(误报)的原因是未定义的符号在#if 指令中被替换为0

标签: c c-preprocessor endianness compile-time


【解决方案1】:

从 C++20 开始,不再需要 hack 或编译器扩展。

https://en.cppreference.com/w/cpp/types/endian

std::endian(在标题<bit>中定义)

enum class endian
{
    little = /*implementation-defined*/,
    big    = /*implementation-defined*/,
    native = /*implementation-defined*/
};
  • 如果所有标量类型都是 little-endian,则 std::endian::native 等于 std::endian::little

  • 如果所有标量类型都是大端,std::endian::native 等于 std::endian::big

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 我记得一开始就用这个方法。问题是:它倾向于在我测试的系统上工作,但它可能不适用于其他一些系统。根据您的可移植性目标,它可能是一种责任,也可能不是。就我而言,由于我不控制我的软件运行的系统,我必须以最大的可移植性为目标。它使这个宏检查不受欢迎。我改为使用运行时测试,它被编译器有效地转换为编译时常量。
    • @ClockZHONG 请注意这里的粗体是两个下划线(即__),所以将LITTLE_ENDIAN读作__LITTLE_ENDIAN__
    • @Cyan 我记得在旧版本的 PPC 工具链中,他们使用 LITTLE_ENDIAN 和 BIG_ENDIAN 来判断平台字节序配置,但是对于较新版本的 GCC,似乎__BYTE_ORDER__&__ORDER_LITTLE_ENDIAN__&__ORDER_BIG_ENDIAN__ 是一个更兼容的解决方案,您可以查看它在这里:gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
    • @Czipperz 谢谢!我现在知道如何在评论中显示原始代码样式了,在你提醒之前我真的没有注意到。
    【解决方案3】:

    正如大多数人所提到的,编译时间是您最好的选择。假设您不进行交叉编译并且您使用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 上......但至少它在编译其余代码时为您提供了字节序并且可以工作对于大多数项目。

    【讨论】:

      【解决方案4】:

      我冒昧地重新格式化了引用的文本

      截至 2017-07-18,我使用 union { unsigned u; unsigned char c[4]; }

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

      使用起来可能会更好

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

      【讨论】:

        【解决方案5】:

        Socket 的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;
        }
        

        【讨论】:

          【解决方案6】:

          在 C 中编译时,您只能信任预处理器 #defines,并且没有标准解决方案,因为 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 之前是宏 #defined)

          【讨论】:

          • 除了最后一个存储的联合成员的值是 C 中未指定的行为。
          • @ouah:C 标准对字节顺序一无所知,所以我们已经走出标准领域并致力于实现特定的行为(而且我认为你永远找不到编译器以不同的方式实现unions 或优化器与它们混淆)。虽然,我同意其他“经典方法”(指向char * 的指针的转换)由于别名规则的例外而不会出现 UB 问题。
          • @ouah:另外,第 6.7.2.1 节没有提到 UB,它只是说“任何时候最多可以将一个成员的值存储在联合对象中”;另外,我敢说 §6.7.2.1 ¶14 隐含地允许使用 unions 作为该演员的替代品,因为“一个指向联合对象的指针,经过适当转换,指向它的每个成员 [.. .] 反之亦然。”。所以,&amp;u.i = &amp;u = &amp;u.c(带有适当的演员表),因此u.c[0] = (*(&amp;u.c))[0]=*((char *)&amp;u.i),与“其他方法”一样合法。
          • 在 C99 中,附件 J(非规范性)“J.1 未指定的行为。以下未指定:除了最后一个存储到 (6.2.6.1) 中的联合成员的值。”和 6.2.6.1p7 说“当一个值存储在联合类型对象的成员中时,不对应于该成员但对应于其他成员的对象表示的字节采用未指定的值。”
          • @ouah:第一个问题是通过处理 §6.7.2.1 ¶14 来解决的,正如我之前已经写过的那样(它仍然是未指定的行为,但就像演员表一样 - 嘿,那个代码是那里可以准确了解编译器是如何实现“未指定行为”的)。您的第二个引用无关紧要,因为我工会中的两个成员大小相同,因此两个成员都“完全填充”了union(即使我声明了一个char,这仍然成立,因为最大的成员首先存储)。
          【解决方案7】:

          我知道我参加这个聚会迟到了,但这是我的看法。

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

          这是基于 '0' 在十进制中是 48 而 '1' 是 49 的事实,所以 '1' 设置了 LSB 位,而 '0' 没有设置。我可以将它们设为'\x00''\x01',但我认为我的版本使其更具可读性。

          【讨论】:

          • 一个重要的问题是,这个版本可以被编译器优化掉吗?答案是:大部分是肯定的,虽然不如数字版本:godbolt.org/g/GhtEYW
          • 有趣的想法,但可能违反uint16_t 的对齐要求。 (它是一些嵌入式处理器的UB代码)。
          【解决方案8】:

          这来自 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;
          }
          

          【讨论】:

            【解决方案9】:

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

            但是,有时可能首选宏。

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

            例如,GCC 推荐:__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__。但是,这只适用于最新版本,早期版本(和其他编译器)会给这个测试一个假值“真”,因为 NULL == NULL。所以你需要更完整的版本:defined(__BYTE_ORDER__)&amp;&amp;(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)

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

            您可以尝试__BIG_ENDIAN____BIG_ENDIAN_BIG_ENDIAN,它们通常在大端编译器上定义。

            这将改善检测。但是,如果您专门针对 PowerPC 平台,您可以添加更多测试以改进更多检测。试试_ARCH_PPC__PPC____PPCPPC__powerpc____powerpc 甚至powerpc。将所有这些定义绑定在一起,您就有相当大的机会检测大端系统,尤其是 powerpc,无论编译器及其版本如何。

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

            【讨论】:

            • 写给其他觉得这个答案有用的人。 gcc 从大约 4.6 开始支持 __BYTE_ORDER__,从 3.2 开始支持 clang
            【解决方案10】:
            #define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)
            

            【讨论】:

            • 如何检测字节序?
            • 带有字节移位。 1 >> 1 在小端拱上为 0。 (移位后该位丢失,因此其值为 0)。为了便于阅读,可以将其扭曲为枚举。
            • 很好,但同样的情况也会发生在任何字节序上。字节顺序是数据的存储方式,而不是移位的完成方式。
            • little endian: 1 is 0000 00001, shift后是0000 0000 big endian: 1 is 0001 0000, shift后是0000 1000 我错了吗?
            • 这就是字节在内存中的存储方式,但操作不是按字节进行的,而是在与字节序无关的整个数字上完成的。
            【解决方案11】:

            尝试类似:

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

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

            【讨论】:

            • 此特定示例无法编译。也许它打算在 C++ 上使用?似乎它不尊重 C 对 var 初始化的限制。
            • 它是用当前的 C 语言而不是 C89 编写的。您可能正在使用像 MSVC 这样的向后编译器,在这种情况下,您需要对其进行一些调整..
            • 确实,我正在使用 MSVC 和 GCC 测试源代码。该代码应该适用于两者。
            【解决方案12】:

            与其寻找编译时检查,不如直接使用大端顺序(许多人认为"network order")并使用大多数UNIX 系统和Windows 提供的htons/htonl/ntohs/ntohl 函数。他们已经被定义为做你想做的工作。为什么要重新发明*?

            【讨论】:

            • 好点。不幸的是,我现在无法更改此约定,因为该代码已经使用了很长时间,并且需要与现有用户数据保持兼容。
            • @Cyan - 啊。在这种情况下,您将不得不使用 autoconf 之类的东西运行构建时检查来为您定义宏,或者选择运行时解决方案。
            • 这仅适用于没有任何 64 位数据类型的情况。至少在 linux 上,htonl 返回 uint32_t,而不是 unsigned long,因此即使在 64 位平台上,它也应该在 32 位值上运行。这通常是函数与现有网络代码一起正常工作所需的行为。
            • @BrianMcFarland - 是的,当我翻阅手册页时,我想,“我记得上次遇到这个函数系列时更有用。”我想人们通常必须实现自己的平台相关包装器。我想知道是否有任何编译器将各种运行时字节序检查习语优化为编译时常量以减少代码执行路径?
            【解决方案13】:

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

            希望这会有所帮助!

            【讨论】:

            • I've heard这种方法在交叉编译环境中可能会导致问题,目标系统的字节序与构建系统的字节序不同。
            • 您应该提供使用 Autotools 的 AC_C_BIGENDIAN 的示例。这是搜索“Autoconf AC_C_BIGENDIAN”的第一个 Stack Overflow 参考,但它缺少我希望的示例。
            【解决方案14】:

            在 C 中使用预处理器指令检测字节顺序是不可能的。

            【讨论】:

              【解决方案15】:

              您无法在编译时检测到它可以在所有编译器之间移植。也许您可以更改代码以在运行时执行此操作 - 这是可以实现的。

              【讨论】:

                【解决方案16】:

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

                这是一个功能:

                bool IsLittleEndian () {
                
                    int i=1;
                
                    return (int)*((unsigned char *)&i)==1;
                
                }
                

                【讨论】: