C宏定义是确定大端还是小端机器?


107

是否有一个单行宏定义来确定计算机的字节序。我正在使用以下代码,但将其转换为宏将太长。

unsigned char test_endian( void )
{
    int test_var = 1;
    unsigned char *test_endian = (unsigned char*)&test_var;

    return (test_endian[0] == 0);
}

2
为什么不将相同的代码包含到宏中?
sharptooth 2010年

4
您不能仅凭C预处理器来轻便地确定字节序。您还需要0而不是NULL在最终测试中,将其中一个test_endian对象更改为其他对象:-)。
Alok Singhal 2010年

2
还有为什么需要一个宏?内联函数将执行相同操作,并且更加安全。
sharptooth 2010年

13
@Sharptooth,一个宏很吸引人,因为它的值可能在编译时就知道了,这意味着您可以使用平台的字节序来控制模板实例化,例如,甚至可以使用#if指令选择不同的代码块。
罗伯·肯尼迪

3
没错,但是效率低下。如果我有一个低位字节序的cpu,并且正在将低位字节序数据写入到连接线或文件中,我宁愿避免无目的地拆包和重新打包数据。我曾经写视频驱动程序为生。将像素写入视频卡以优化每个位置时,这非常重要。
爱德华·福尔克

Answers:


102

支持任意字节顺序的代码,已经准备好放入称为的文件中order32.h

#ifndef ORDER32_H
#define ORDER32_H

#include <limits.h>
#include <stdint.h>

#if CHAR_BIT != 8
#error "unsupported char size"
#endif

enum
{
    O32_LITTLE_ENDIAN = 0x03020100ul,
    O32_BIG_ENDIAN = 0x00010203ul,
    O32_PDP_ENDIAN = 0x01000302ul,      /* DEC PDP-11 (aka ENDIAN_LITTLE_WORD) */
    O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 (aka ENDIAN_BIG_WORD) */
};

static const union { unsigned char bytes[4]; uint32_t value; } o32_host_order =
    { { 0, 1, 2, 3 } };

#define O32_HOST_ORDER (o32_host_order.value)

#endif

您将通过检查小端系统

O32_HOST_ORDER == O32_LITTLE_ENDIAN

11
但是,这直到运行时才让您决定字节顺序。以下原因无法编译。/ ** isLittleEndian :: result-> 0或1 * / struct isLittleEndian {枚举isLittleEndianResult {结果=(O32_HOST_ORDER == O32_LITTLE_ENDIAN)}}; };
user48956

3
直到运行时才能获得结果吗?
k06a 2010年

8
为什么charuint8_t如果此类型不可用(可以通过检查#if UINT8_MAX),请更好地使用并失败。请注意,该设置CHAR_BIT独立于uint8_t
Andreas Spindler


3
为了完整起见,让我再扔一点:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
爱德华·福克

49

如果您的编译器支持C99复合文字:

#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})

要么:

#define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c)

通常,您应该尝试编写不依赖于主机平台字节序的代码。


与主机端序无关的实现示例ntohl()

uint32_t ntohl(uint32_t n)
{
    unsigned char *np = (unsigned char *)&n;

    return ((uint32_t)np[0] << 24) |
        ((uint32_t)np[1] << 16) |
        ((uint32_t)np[2] << 8) |
        (uint32_t)np[3];
}

3
“您应该尝试编写不依赖于主机平台字节序的代码”。不幸的是,我的请求“我知道我们正在编写POSIX兼容性层,但我不想实现ntoh,因为它取决于主机平台的字节序”,总是让人耳聋;-)。图形格式处理和转换代码是我见过的另一个主要候选对象-您不想一直以调用ntohl为基础。
史蒂夫·杰索普

5
您可以采用ntohl不依赖于主机平台字节顺序的方式来实现。
caf 2010年

1
@caf您将如何以与主机字节序无关的方式编写ntohl?
HayriUğurKoltuk'3

3
@AliVeli:我在答案中添加了示例实现。
caf 2012年

6
我还应该添加一条记录,即无论如何优化,至少在gcc 4.5.2中,“(*(uint16_t *)” \ 0 \ xff“ <0x100)”都不会编译为常量。它总是创建可执行代码。
爱德华·福克

43

没有标准,但是在许多系统上,包括<endian.h>都会为您提供一些要查找的定义。


30
使用#if __BYTE_ORDER == __LITTLE_ENDIAN和测试字节序#elif __BYTE_ORDER == __BIG_ENDIAN。并生成一个#error否则。
2011年

6
<endian.h>在Windows上不可用
rustyx '16

2
安卓的项目中使用endian.h,除非__APPLE___WIN32定义。
patryk.beza

1
在OpenBSD 6.3中,<endian.h> 在名称之前提供#if BYTE_ORDER == LITTLE_ENDIAN(或BIG_ENDIAN)没有下划线。_BYTE_ORDER仅用于系统头。__BYTE_ORDER不存在。
乔治·科勒

@ To1ne我怀疑Endianness是否与Windows有关,因为Windows(至少当前)仅在x86和ARM计算机上运行。x86始终可以配置为LE和ARM,以使用两种体系结构。
SimonC '18

27

要在运行时检测字节序,您必须能够引用内存。如果您坚持使用标准C,则在内存中声明变量需要一条语句,而返回值则需要表达式。我不知道如何在单个宏中执行此操作-这就是为什么gcc具有扩展名:-)

如果您愿意使用.h文件,则可以定义

static uint32_t endianness = 0xdeadbeef; 
enum endianness { BIG, LITTLE };

#define ENDIANNESS ( *(const char *)&endianness == 0xef ? LITTLE \
                   : *(const char *)&endianness == 0xde ? BIG \
                   : assert(0))

然后您就可以使用该ENDIANNESS宏了。


6
我之所以喜欢它,是因为它承认除了大小写之外,还存在字节顺序。
Alok Singhal 2010年

6
说到这,可能值得调用宏INT_ENDIANNESS甚至UINT32_T_ENDIANNESS,因为它仅测试一种类型的存储表示。有一个ARM ABI,其中整数类型为little-endian,而double类型为middle-endian(每个单词均为little-endian,但是带有符号位的单词位于另一个单词之前)。我可以告诉您,这在一天左右的编译器团队中引起了兴奋。
史蒂夫·杰索普

19

如果只想依赖预处理器,则必须找出预定义符号的列表。预处理器算术没有寻址的概念。

Mac上的 GCC 定义__LITTLE_ENDIAN____BIG_ENDIAN__

$ gcc -E -dM - < /dev/null |grep ENDIAN
#define __LITTLE_ENDIAN__ 1

然后,您可以基于平台检测#ifdef _WIN32等添加更多预处理器条件指令。


6
尽管GCC 4.0.1和4.2.1在Macintosh上定义了这些宏,但Linux上的GCC 4.1.2似乎没有定义这些宏。因此,即使允许您指定使用哪个编译器,它也不是用于跨平台开发的可靠方法。
罗伯·肯尼迪

1
哦,是的,因为它仅由Mac上的GCC定义。
2011年

注意:我的GCC(在Mac上)定义#define __BIG_ENDIAN__ 1#define _BIG_ENDIAN 1

适用于OpenBSD / amd64的clang 5.0.1具有#define __LITTLE_ENDIAN__ 1。这个宏似乎是一个clang功能,而不是gcc功能。gcc在某些Mac中,该命令不是gcc,而是clang。
乔治·科勒

Mac上的GCC 4.2.1当时为GCC
Gregory Pakosz '18年

15

我相信这就是所要的。我仅在msvc下的小端计算机上对此进行了测试。有人请在一台大字节序的机器上确认。

    #define LITTLE_ENDIAN 0x41424344UL 
    #define BIG_ENDIAN    0x44434241UL
    #define PDP_ENDIAN    0x42414443UL
    #define ENDIAN_ORDER  ('ABCD') 

    #if ENDIAN_ORDER==LITTLE_ENDIAN
        #error "machine is little endian"
    #elif ENDIAN_ORDER==BIG_ENDIAN
        #error "machine is big endian"
    #elif ENDIAN_ORDER==PDP_ENDIAN
        #error "jeez, machine is PDP!"
    #else
        #error "What kind of hardware is this?!"
    #endif

作为附带说明(特定于编译器),对于激进的编译器,您可以使用“消除死代码”优化来达到与编译时相同的效果,#if如下所示:

    unsigned yourOwnEndianSpecific_htonl(unsigned n)
    {
        static unsigned long signature= 0x01020304UL; 
        if (1 == (unsigned char&)signature) // big endian
            return n;
        if (2 == (unsigned char&)signature) // the PDP style
        {
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        if (4 == (unsigned char&)signature) // little endian
        {
            n = (n << 16) | (n >> 16);
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        // only weird machines get here
        return n; // ?
    }

以上依赖于编译器可以识别在编译时的常数值的事实,完全消除了代码中if (false) { ... }并取代这样的代码if (true) { foo(); }foo();最坏的情况:编译器不会做了优化,你仍然可以得到正确的代码,但有点慢。


我喜欢这种方法,但是如果我错了,请更正我:仅当您在要为其构建的机器上编译时,此方法才有效,对吗?
leetNightshade 2012年

3
由于多字符字符常量,gcc也会引发错误。因此,不便携。
爱德华·福克

2
什么编译器让您编写'ABCD'
Ryan Haining

2
许多编译器会在宽松的遵从模式下允许多字节字符常量,但是使用顶部运行clang -Wpedantic -Werror -Wall -ansi foo.c则会出错。(锵和这个具体:-Wfour-char-constants -Werror

@Edward Falk 在代码中具有多字符常量不是错误。它是实现定义的行为C11 6.4.4.4。10.根据设置,gcc和其他可能会/可能不会警告/错误,但这不是C错误。使用多字符字符常量当然并不流行。
chux-恢复莫妮卡

10

如果您正在寻找编译时测试并且正在使用gcc,则可以执行以下操作:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

有关更多信息,请参见gcc文档


3
对于使用gcc的任何人来说,这绝对是最好的答案
rtpax

2
__BYTE_ORDER__自GCC 4.6
Benoit Blanchon'Mar

8

实际上,您可以使用复合文字(C99)访问临时对象的内存:

#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1})

哪个GCC将在编译时进行评估。


我喜欢。有没有一种可移植的编译时方式来知道您正在C99下进行编译?
爱德华·福克

1
哦,如果不是GCC怎么办?
爱德华·福克

1
@EdwardFalk是的。#if __STDC_VERSION__ >= 199901L
詹斯

7

“ C网络库”提供了处理字节序的功能。即htons(),htonl(),ntohs()和ntohl()...其中n是“网络”(即big-endian),h是“主机”(即,运行码)。

这些表面上的“功能”(通常)定义为宏[请参见<netinet / in.h>],因此使用它们不会产生运行时开销。

以下宏使用这些“功能”来评估字节序。

#include <arpa/inet.h>
#define  IS_BIG_ENDIAN     (1 == htons(1))
#define  IS_LITTLE_ENDIAN  (!IS_BIG_ENDIAN)

此外:

我唯一需要知道系统字节序的时间是当我将一个变量[写到文件/其他]写出时,该变量可以由另一个字节序未知的系统读取(以实现跨平台兼容性) )...在这种情况下,您可能希望直接使用endian函数:

#include <arpa/inet.h>

#define JPEG_MAGIC  (('J'<<24) | ('F'<<16) | ('I'<<8) | 'F')

// Result will be in 'host' byte-order
unsigned long  jpeg_magic = JPEG_MAGIC;

// Result will be in 'network' byte-order (IE. Big-Endian/Human-Readable)
unsigned long  jpeg_magic = htonl(JPEG_MAGIC);

这并没有真正回答一直在寻找确定字节序的快速方法的问题。
Oren 2013年

@Oren:关于您的有效批评,我已经添加了一些细节,以便更直接地解决原始问题。
BlueChip

6

使用内联函数而不是宏。此外,您需要将某些东西存储在内存中,这是宏的不太好的副作用。

您可以使用静态或全局变量将其转换为短宏,如下所示:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)

我认为这是最好的,因为它是最简单的。但是,它不能针对混合字节序进行测试
HayriUğurKoltuk

1
为什么不s_endianess将其设置为1?
SquareRootOfTwentyThree,

5

尽管没有可移植的#define或要依赖的东西,但平台确实提供了用于与“主机”字节序之间来回转换的标准功能。

通常,使用“网络字节序”(BIG字节序)和使用主机字节序(在x86上为LITTLE字节序)的本地计算来存储到磁盘或网络。您可以使用htons()ntohs()和朋友在两者之间进行转换。


4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

6
这还会生成可执行代码,而不是常量。您无法执行“ #if IS_BIG_ENDIAN”
爱德华·福尔克

据我了解,我喜欢此解决方案,因为它不依赖C / C ++标准的未定义行为。这不是编译时间,但是唯一的标准解决方案是等待c ++ 20 std :: endian
ceztko

4

不要忘记字节序不是全部-字节大小char可能不是8位(例如DSP),不能保证二进制补码(例如Cray),可能需要严格的对齐方式(例如SPARC,ARM也会陷入困境) -endian(未对齐时)等)

而是针对特定的CPU体系结构可能是一个更好的主意。

例如:

#if defined(__i386__) || defined(_M_IX86) || defined(_M_IX64)
  #define USE_LITTLE_ENDIAN_IMPL
#endif

void my_func()
{
#ifdef USE_LITTLE_ENDIAN_IMPL
  // Intel x86-optimized, LE implementation
#else
  // slow but safe implementation
#endif
}

请注意,不幸的是,该解决方案也不是超级可移植的,因为它取决于特定于编译器的定义(没有标准,但这对此类定义的很好的编译)。


3

试试这个:

#include<stdio.h>        
int x=1;
#define TEST (*(char*)&(x)==1)?printf("little endian"):printf("Big endian")
int main()
{

   TEST;
}

2

请注意,这里的大多数答案都不是可移植的,因为当今的编译器将在编译时评估这些答案(取决于优化),并根据特定的字节序返回特定的值,而实际的计算机字节序可以有所不同。测试字节序的值永远不会到达系统内存,因此实​​际执行的代码将返回相同的结果,而不管实际的字节序如何。

对于例如,在ARM的Cortex-M3的执行字节顺序将反映在状态位AIRCR.ENDIANNESS和编译器不知道在编译时该值。

编译输出为此处建议的一些答案:

https://godbolt.org/z/GJGNE2对于答案,

https://godbolt.org/z/Yv-pyJ对于答案,依此类推。

要解决它,您将需要使用volatile限定符。Yogeesh H T的答案是当今现实生活中最接近的答案,但是由于Christoph建议了更全面的解决方案,因此对他的答案稍作修正即可使答案完整,只需将其添加volatile到联合声明中即可:static const volatile union

这样可以确保确定端序需要存储和从内存中读取。


2

如果转储预处理器#defines

gcc -dM -E - < /dev/null
g++ -dM -E -x c++ - < /dev/null

通常,您会找到可以帮助您的东西。具有编译时逻辑。

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

但是,各种编译器可能具有不同的定义。


0

我的答案没有问到,但查找系统是小端还是大端真的很简单

码:

#include<stdio.h>

int main()
{
  int a = 1;
  char *b;

  b = (char *)&a;
  if (*b)
    printf("Little Endian\n");
  else
    printf("Big Endian\n");
}

0

C代码,用于检查系统是小端还是大印度。

int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // aliasing through char is ok
    puts("This system is little-endian");
else
    puts("This system is big-endian");

-3

宏查找端基

#define ENDIANNES() ((1 && 1 == 0) ? printf("Big-Endian"):printf("Little-Endian"))

要么

#include <stdio.h>

#define ENDIAN() { \
volatile unsigned long ul = 1;\
volatile unsigned char *p;\
p = (volatile unsigned char *)&ul;\
if (*p == 1)\
puts("Little endian.");\
else if (*(p+(sizeof(unsigned long)-1)) == 1)\
puts("Big endian.");\
else puts("Unknown endian.");\
}

int main(void) 
{
       ENDIAN();
       return 0;
}

3
第一个宏不正确,并且将始终返回“ Big-Endian”。移位不受字节序的影响-字节序仅影响读取和存储到存储器。
GaspardP
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.