【问题标题】:Determine if the compiler is little or big endian in C确定编译器在 C 中是小端还是大端
【发布时间】:2016-05-09 14:50:04
【问题描述】:

谁能告诉我这个程序如何确定编译器是小端还是大端。

#include <stdio.h>
#include <sys/types.h>
int main(void) {
    union {
       long lungo;
       char ch[sizeof(long)];
    } unione;
    unione.lungo = 1;
    if (unione.ch[sizeof(long)-1] == 0)
       printf("little endian\n");
    else
       printf("big endian\n");
    return (0);
}

特别是我不明白这部分程序的作用:

union {
       long lungo;
       char ch[sizeof(long)];
    } unione;

谢谢

【问题讨论】:

  • @SandBag_1996:通过工会为char 起别名是合法的。
  • 请使用 C 书籍了解 union 是什么。这是你的主要问题。
  • "little or big endian"` 是 2 种常见的 endian - 其他的存在,但往往很少见。
  • @SandBag_1996:两者是不同语言的另一个原因。
  • @chux:大端编译器可以很好地为小端目标生成代码。这对于交叉编译器来说是典型的。编译器的字节序对翻译后的程序代码无关紧要,而与目标的字节序无关。所以他是非常正确的(也许我们中的一个人误解了他的意思?)。

标签: c endianness


【解决方案1】:

一个联合的所有成员都占用相同的内存,因此它们相互重叠;写信给工会的一个成员会更新工会的所有个成员。

您已设置 unione 来存储 2 个成员; lungolongchchar 的数组,大小可容纳 sizeof long 字节(4 或 8 字节,取决于您的系统;在本次讨论中,我们假设 4 字节)。这两个成员相互重叠(它们占用相同的 4 字节内存)。

请记住,在大端系统中,多字节类型的最重要字节将存储在地址 A,最不重要字节将存储在地址 A+3。在 little-endian 系统上,该顺序是相反的;最低有效字节将存储在地址 A,最高有效字节将存储在地址 A+3:

   BE:  A     A+1   A+2   A+3     where A is arbitrary address
        ----  ----  ----  ----
lungo:  0x00  0x00  0x00  0x01
        ----  ----  ----  ----
   LE:  A+3   A+2   A+1   A

另一方面,数组总是这样存储的,a[0] 存储在地址 A,a[1] 存储在 A+1,等等。所以如果我们查看 chlungo 的关系在大端和小端系统上,我们看到以下内容:

   BE: ch[0]  ch[1]  ch[2]  ch[3]
       -----  -----  -----  -----
lungo: 0x00   0x00   0x00   0x01
       -----  -----  -----  -----
   LE: ch[3]  ch[2]  ch[1]  ch[0]

因此,在 little-endian 系统上,ch[0] 对应于 lungo 的最低有效字节,其中包含值 0x01。在大端系统上,ch[0] 对应于lungo 的最高有效字节,其中包含值0x00

这是确定字节顺序的常用技巧,但严格来说,该行为是未定义的;您不应该写信给工会的一个成员并从另一个成员那里阅读。这种技术“有效”是因为任何多字节类型都可以干净地映射到charunsigned char 的数组上,但通常不能在两种多字节类型之间干净地应用它。

没有 100% 可移植、符合标准的方法来确定系统的字节顺序,至少我所知道的没有。我所知道的所有这些都涉及像这样的技巧或某种类型的双关语,比如

long l = 0x00010203;
char *c = (char *) &l; 
if ( c[0] == 0x03 )
  // little-endian
else if ( c[0] == 0x00 )
  // big-endian
else 
  // something else

同样,这不是好的做法,可能未定义,但它在大多数情况下“有效”。

Big-endianness 和 little-endianness 并不是唯一可能的排序方式,而且在单个系统上可能有多个排序方式。 VAXen 通常是 little-endian,除了 32 位浮点数,它们是“middle-endian”,布局为 2301。

【讨论】:

  • 我记得,写入一个union 成员并从unsigned char [] 的字段(相同大小)读取不是未定义的行为。
【解决方案2】:
union {
       long lungo;
       char ch[sizeof(long)];
    } unione;

这是一个联合。它的成员占用相同的内存区域。写入一个成员(在这种情况下为整数)是一种常见的技巧......

unione.lungo = 1;

...然后通过另一个(通常是 char 数组)读回...

if (unione.ch[sizeof(long)-1] == 0)

...获取前一种类型的字节表示。 (在这种情况下,整数的“第一个”字节,以确定存储在整数中的1 是否显示在其中(这将使它成为一个小端平台),或者不(这将使它成为一个大-endian 平台)。注意@chux 评论——其他形式的字节序确实存在,尽管它们非常罕见。

附录 J.1 将“存储到的最后一个以外的联合成员的值”指定为未指定

附录 J.3.13 将“任何对象中字节的数量、顺序和编码(当本国际标准中未明确指定时)”指定为实现定义

第 6.2.6 节(“类型的表示”)指定“某些对象表示不需要表示对象类型的值。如果存储的 对象的值具有这样的表示,并且被没有字符类型的左值表达式读取,行为是undefined。”

所以,虽然不是未定义的行为,但这个构造是......比方说,走近边线。 ;-)

【讨论】:

  • 这是合法的,但不好的做法,因为它依赖于太多特定于实现的变量。有一些符合标准的可移植方式来(反)序列化/编组标准类型。
【解决方案3】:

如果你的编译器有uint16_t#define IS_BIG_ENDIAN (!*(unsigned char*)(void*)&amp;(uint16_t){1}) 是一个很好的方法。但是很可能还有其他“字节顺序”方案可能会给您带来误报。我不知道。在这一点上,我欢迎举个反例来投反对票!

从技术上讲,使用“联合技巧”的行为在 C++ 中是未定义的,尽管您可以在 C 中摆脱它。

通过(void*) 进行强制转换在 C 中是多余的,但在 C++ 中技术上是必需的。

【讨论】:

  • 这违反了有效类型规则,因此调用了UB。联合在 C 中是完全合法的。但如果您只想编组对象,这不是一个好主意。我认为 gcc 至少为此提供了宏(但不确定,它也可能是库)。
  • @Olaf:恐怕标准库中没有宏。 (至少在 C99 中没有,我对 C11 还没有 100% 的信心。)
  • @Olaf:您能解释一下为什么您认为这是 UB,并尽可能引用标准吗?在 n1548 中,第 6.5.7 节明确允许通过指向字符类型的指针访问另一种类型的对象。 AFAIK 这一直是类型别名规则的例外。 (例如,如果没有这个例外,就不可能以可移植的方式实现memcpy。)
  • @DevSolar:对不起,我不清楚:我不是指 C 标准库,而是 glibc 或 newlib。 IIRC,我在一些 gcc 项目中看到了类似的东西,我确信它来自工具链,而不是应用程序代码。但那是很久以前的事了,现在这种序列化只是一种不好的做法。
  • @NateEldredge:n1548 是一个过时的草案,所以我什至不会考虑它。仅使用 n1570(这在技术上与最终标准相同)。重新点:对不起,我以某种方式误解了这一点。这只是void * 的中间转换,这是不必要的。我将虚拟编辑问题并删除 DV。我头上的灰烬。
猜你喜欢
  • 2010-09-18
  • 2014-02-22
  • 2011-07-22
  • 1970-01-01
  • 1970-01-01
  • 2010-11-03
  • 2010-09-26
  • 2011-05-10
  • 2011-01-07
相关资源
最近更新 更多