在编译时确定 endianness

是否有一种安全的、可移植的方法来确定(在编译期间)我的程序所在的平台的 endianness?我用 C 写。

[编辑] 感谢您的回答,我决定坚持使用运行时解决方案!

52059 次浏览

据我所知,不,不是在编译期间。

在运行时,可以进行一些简单的检查,例如将多字节值设置为已知位字符串,并检查结果是什么字节。例如使用联合,

typedef union {
uint32_t word;
uint8_t bytes[4];
} byte_check;

或者选角,

uint32_t word;
uint8_t * bytes = &word;

请注意,对于完全可移植的 endianness 检查,需要同时考虑 big-endian、 little-endian 和 mix-endian 系统。

这是用于编译时检查的

您可以使用来自升级头文件 endian.hpp的信息,它涵盖了许多平台。

编辑以进行运行时检查

bool isLittleEndian()
{
short int number = 0x1;
char *numPtr = (char*)&number;
return (numPtr[0] == 1);
}

创建一个整数,并读取其第一个字节(最小有效字节)。如果那个字节是1,那么系统就是 little endian,否则就是 big endian。

想一想

是的,你可能会遇到一个潜在的问题在某些平台(不能想到任何) ,其中 sizeof(char) == sizeof(short int)。您可以使用 <stdint.h>中提供的固定宽度多字节整数类型,或者如果您的平台没有这种类型,您也可以为您的使用调整一个升压头: stdint.hpp

不是在编译时,而是在运行时:

/*  Returns 1 if LITTLE-ENDIAN or 0 if BIG-ENDIAN  */
#include <inttypes.h>
int endianness()
{
union { uint8_t c[4]; uint32_t i; } data;
data.i = 0x12345678;
return (data.c[0] == 0x78);
}

常见问题的报道很有意思:

你可能做不到,通常检测 Endianness 的方法 包括指针或字符数组,或者联合,但是预处理器 算术只使用长整数,并且没有 另一个诱人的可能性是

  #if 'ABCD' == 0x41424344

但这也不可靠。

使用 C99,您可以执行以下检查:

#define I_AM_LITTLE (((union { unsigned x; unsigned char c; }){1}).c)

if (I_AM_LITTLE)这样的条件将在编译时计算,并允许编译器优化整个块。

我没有关于这是否是严格意义上的 C99中的 常数表达式(它允许在静态存储持续时间数据的初始化器中使用它)的参考,但如果不是,它是次好的事情。

我曾经用过这样一个结构:

uint16_t  HI_BYTE  = 0,
LO_BYTE  = 1;
uint16_t  s = 1;


if(*(uint8_t *) &s == 1) {
HI_BYTE = 1;
LO_BYTE = 0;
}


pByte[HI_BYTE] = 0x10;
pByte[LO_BYTE] = 0x20;

具有 -O2的 gcc 能够使其完全编译时间。这意味着,HI_BYTELO_BYTE变量被完全替换,甚至 pByte 访问在汇编程序中被等效于 *(unit16_t *pByte) = 0x1020;的变量替换。

它的编译时间已经足够了。

我想扩展为 C + + 提供 constexpr函数的答案

union Mix {
int sdat;
char cdat[4];
};
static constexpr Mix mix { 0x1 };
constexpr bool isLittleEndian() {
return mix.cdat[0] == 1;
}

因为 mix也是 constexpr,它是编译时间,可以在 constexpr bool isLittleEndian()中使用。应该是安全的使用。

更新

正如@Cheersandth 在下面指出的,这些似乎是有问题的。

原因是,它是 非 C + + 11-符合标准,其中 字体双关是禁止的。每次总是只能有一个工会成员是 激活。使用符合标准的编译器,您将得到一个错误。

那么,C + + 中的 别用。看起来,你可以用 C 语言来做。我留下我的答案是为了教育的目的: ——)而且因为这个问题是关于 C。.

更新2

这里假设 int的大小为4个 char,这并不总是像下面@PetrVep 正确指出的那样给出。要使代码真正具有可移植性,您必须更加聪明。不过,对于许多情况来说,这应该足够了。注意,根据定义,sizeof(char)始终是 1。上面的代码假定为 sizeof(int)==4

为了回答 编译时检查的最初问题,没有一种标准化的方法可以在所有现有的和未来的编译器中使用,因为现有的 C、 C + + 和 POSIX 标准都没有定义用于检测 endianness 的宏。

但是,如果您愿意将自己限制在一些已知的编译器集合中,那么可以查找每个编译器的文档,找出它们使用哪些预定义的宏(如果有的话)来定义 endianness。这一页列出了几个你可以查找的宏,所以这里有一些适用于这些宏的代码:

#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
defined(__BIG_ENDIAN__) || \
defined(__ARMEB__) || \
defined(__THUMBEB__) || \
defined(__AARCH64EB__) || \
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
// It's a big-endian target architecture
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
defined(__LITTLE_ENDIAN__) || \
defined(__ARMEL__) || \
defined(__THUMBEL__) || \
defined(__AARCH64EL__) || \
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
// It's a little-endian target architecture
#else
#error "I don't know what architecture this is!"
#endif

如果你不能从文档中找到你的编译器所使用的预定义宏,你也可以尝试强迫它吐出它的预定义宏的完整列表,然后从中猜测什么可以工作(寻找任何包含 ENDIAN,ORDER,或者处理器架构名称的东西)。这一页列出了许多在不同编译器中实现这一点的方法:

Compiler                   C macros                         C++ macros
Clang/LLVM                 clang -dM -E -x c /dev/null      clang++ -dM -E -x c++ /dev/null
GNU GCC/G++                gcc   -dM -E -x c /dev/null      g++     -dM -E -x c++ /dev/null
Hewlett-Packard C/aC++     cc    -dM -E -x c /dev/null      aCC     -dM -E -x c++ /dev/null
IBM XL C/C++               xlc   -qshowmacros -E /dev/null  xlc++   -qshowmacros -E /dev/null
Intel ICC/ICPC             icc   -dM -E -x c /dev/null      icpc    -dM -E -x c++ /dev/null
Microsoft Visual Studio (none)                              (none)
Oracle Solaris Studio      cc    -xdumpmacros -E /dev/null  CC      -xdumpmacros -E /dev/null
Portland Group PGCC/PGCPP  pgcc  -dM -E                     (none)

最后,为了圆满解决这个问题,微软的 Visual C/C + + 编译器是比较奇怪的,没有以上任何一种。幸运的是,他们已经记录了预定义的宏 给你,您可以使用目标处理器体系结构来推断字节顺序。尽管 Windows 当前支持的所有处理器都是 little-endian (_M_IX86_M_X64_M_IA64_M_ARM是 little-endian) ,但是一些历史上支持的处理器,如 PowerPC (_M_PPC)是 big-endian。但更重要的是,Xbox 360是一个大容量 PowerPC 机器,所以如果您正在编写一个跨平台库头,检查 _M_PPC也不会有什么坏处。

来自 最后,在 C 语言预处理器中进行一行字节顺序检测:

#include <stdint.h>


#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)

任何像样的优化器都会在编译时解决这个问题。

当然,stdint.h是 C99。有关 ANSI/C89的可移植性,请参阅 Doug Gwyn 的 即时 C9x库。

使用 CMake TestBigEndian作为

INCLUDE(TestBigEndian)
TEST_BIG_ENDIAN(ENDIAN)
IF (ENDIAN)
# big endian
ELSE (ENDIAN)
# little endian
ENDIF (ENDIAN)

就我而言,我决定使用一种中间方法: 尝试宏,如果它们不存在,或者我们找不到它们,那么就在运行时执行。下面是一个在 GNU 编译器上运行的程序:

#define II      0x4949     // arbitrary values != 1; examples are
#define MM      0x4D4D     // taken from the TIFF standard


int
#if defined __BYTE_ORDER__ && __BYTE_ORDER__ == __LITTLE_ENDIAN
const host_endian = II;
# elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __BIG__ENDIAN
const host_endian = MM;
#else
#define _no_BYTE_ORDER
host_endian = 1;            // plain "int", not "int const" !
#endif

然后,在实际的代码中:

int main(int argc, char **argv) {
#ifdef _no_BYTE_ORDER
host_endian = * (char *) &host_endian ? II : MM;
#undef _no_BYTE_ORDER
#endif


// .... your code here, for instance:
printf("Endedness: %s\n", host_endian == II ? "little-endian"
: "big-endian");


return 0;
}

另一方面,正如最初的海报所指出的,运行时检查的开销是如此之小(两行代码和微秒级的时间) ,以至于几乎不值得在预处理器中尝试这样做。

我从 Rapidjson 图书馆借来的:

#define BYTEORDER_LITTLE_ENDIAN 0 // Little endian machine.
#define BYTEORDER_BIG_ENDIAN 1 // Big endian machine.


//#define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN


#ifndef BYTEORDER_ENDIAN
// Detect with GCC 4.6's macro.
#   if defined(__BYTE_ORDER__)
#       if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#           define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN
#       elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#           define BYTEORDER_ENDIAN BYTEORDER_BIG_ENDIAN
#       else
#           error "Unknown machine byteorder endianness detected. User needs to define BYTEORDER_ENDIAN."
#       endif
// Detect with GLIBC's endian.h.
#   elif defined(__GLIBC__)
#       include <endian.h>
#       if (__BYTE_ORDER == __LITTLE_ENDIAN)
#           define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN
#       elif (__BYTE_ORDER == __BIG_ENDIAN)
#           define BYTEORDER_ENDIAN BYTEORDER_BIG_ENDIAN
#       else
#           error "Unknown machine byteorder endianness detected. User needs to define BYTEORDER_ENDIAN."
#       endif
// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro.
#   elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
#       define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN
#   elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
#       define BYTEORDER_ENDIAN BYTEORDER_BIG_ENDIAN
// Detect with architecture macros.
#   elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__)
#       define BYTEORDER_ENDIAN BYTEORDER_BIG_ENDIAN
#   elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__)
#       define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN
#   elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
#       define BYTEORDER_ENDIAN BYTEORDER_LITTLE_ENDIAN
#   else
#       error "Unknown machine byteorder endianness detected. User needs to define BYTEORDER_ENDIAN."
#   endif
#endif