在C++程序中以编程方式检测字节顺序

Jay*_*y T 201 c++ algorithm endianness

是否有一种编程方式来检测您是否在大端或小端架构上?我需要能够编写将在Intel或PPC系统上执行的代码并使用完全相同的代码(即没有条件编译).

Dav*_*eau 173

我不喜欢基于类型惩罚的方法 - 它经常会被编译器警告.这正是工会的意义所在!

bool is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1; 
}
Run Code Online (Sandbox Code Playgroud)

该原则相当于其他人建议的类型情况,但这更清楚 - 根据C99,保证是正确的.与直接指针转换相比,gcc更喜欢这个.

这比在编译时修复字节顺序要好得多 - 对于支持多体系结构的操作系统(例如Mac OS x上的胖二进制文件),这对于ppc/i386都有效,否则很容易搞乱. .

  • 我不建议命名变量"bint":) (50认同)
  • 你确定这个定义明确吗?在C++中,联合中只有一个成员可以同时处于活动状态 - 即,您不能使用一个成员名分配并使用另一个成员读取(尽管布局兼容结构有例外) (40认同)
  • 上帝保佑GCC™. (36认同)
  • @Matt:我看了谷歌,而且bint似乎有英文含义,我不知道:) (24认同)
  • 我已经测试了这个,在gcc 4.0.1和gcc 4.4.1中,这个函数的结果可以在编译时确定并作为常量处理.这意味着如果分支仅依赖于此函数的结果并且永远不会在相关平台上进行处理,则编译器将丢弃.对于许多htonl的实现,这可能不是这样. (14认同)
  • 这个解决方案真的很便携吗 如果`CHAR_BIT!= 8`怎么办? (4认同)
  • @zorgit 提出了一个很好的观点。你至少应该使用 `uint8_t` 而不是 `char`。 (4认同)
  • @Faisal:在C++中,一次只有一个成员*保证*可以工作,但实际上所有编译器都实现了你可以从联合中读取的扩展"好像"它们已被赋值给具有相同存储表示的值您实际分配的值.当然假设您读取的类型具有该存储表示的值.当然,如果所有提问者都关心的是英特尔和PPC,并且他正在使用普通的编译器,那么这很好. (3认同)
  • 等一下,这不是技术上未定义的行为,因为您正在从不是分配给的最后一个字段的联合字段中读取数据吗?/sf/answers/839787931/ (3认同)
  • 使用这样的工会是完全有效的,即使是非标准的.GCC和VS都保证访问部分值是可以的.union意味着相同的内存地址,不同的访问方法,所以union {int i; char c [4];}女孩; 意味着设置girl.c [0]会影响girl.i,但行为的"不确定性"是,WHICH BYTE的c [0]对应于i?:}这是endianess部分,未定义,但每个游戏开发人员都知道并使用它. (2认同)
  • 这仅在C(按标准)中起作用,在C ++中是UB。GNUC ++确实支持它。 (2认同)

Eri*_*lje 81

您可以通过设置int和屏蔽掉位来实现,但最简单的方法可能就是使用内置的网络字节转换操作(因为网络字节顺序总是大端).

if ( htonl(47) == 47 ) {
  // Big endian
} else {
  // Little endian.
}
Run Code Online (Sandbox Code Playgroud)

小小的摆弄可能会更快,但这种方式很简单,直截了当,而且非常不可能搞砸.

  • 请注意,在Linux(gcc)上,htonl在编译时会受到常量折叠的影响,因此这种形式的表达式根本没有运行时开销(即它是常量折叠为1或0,然后死代码消除会删除if的其他分支) (7认同)
  • @sharptooth - 慢是相对而言的,但是请注意,如果速度是一个真正的问题,在节目的开始,一旦使用它,并设置一个全局变量与存储方式. (6认同)
  • htonl还有另一个问题:在某些平台上(Windows?),它并不存在于C运行时库中,而是存在于其他网络相关库(socket等)中.如果您不需要库,这对于一个函数来说是一个很大的障碍. (4认同)
  • 此外,在x86上,htonl可以(并且在Linux/gcc上)使用内联汇编程序非常有效地实现,特别是如果您的目标是支持`BSWAP`操作的微架构. (2认同)

And*_*are 62

请看这篇文章:

以下是一些确定机器类型的代码

int num = 1;
if(*(char *)&num == 1)
{
    printf("\nLittle-Endian\n");
}
else
{
    printf("Big-Endian\n");
}
Run Code Online (Sandbox Code Playgroud)

  • 请记住,它取决于int和char是不同的长度,这几乎总是如此,但不能保证. (23认同)
  • 我一直在使用短int和char大小相同的嵌入式系统...我不记得常规int是否也是那个大小(2个字节). (8认同)
  • 为什么这个回答几乎是唯一的答案,这不是让我觉得"伙计,你在做什么?",这是大多数答案的情况:o (2认同)
  • @Shillard int必须至少那么大,但是标准中没有要求将char限制为更少!如果你看看TI F280x系列,你会发现CHAR_BIT是16并且sizeof(int)== sizeof(char),而你提到的限制是绝对正常的...... (2认同)
  • 为什么不使用uint8_t和uint16_t? (2认同)
  • @DavidThornley 如果在你的平台上`sizeof(int)==sizeof(char)`,你根本没有字节序问题。 (2认同)

Lyb*_*rta 41

std::endian如果您可以访问C++ 20编译器,例如GCC 8+或Clang 7+,则可以使用:

#include <bit>

if constexpr (std::endian::native == std::endian::big)
{
    // Big endian system
}
else if constexpr (std::endian::native == std::endian::little)
{
    // Little endian system
}
else
{
    // Something else
}
Run Code Online (Sandbox Code Playgroud)

  • 作为每个人,我可以访问C++ 17和20草稿/提案,但是,截至目前,是否存在任何C++ 20编译器? (5认同)
  • 在该问题的 30 多个答案中,这似乎是唯一一个完全准确的答案(另一个答案至少部分正确)。 (3认同)

bil*_*ill 36

这通常在编译时完成(特别是出于性能原因),使用编译器提供的头文件或创建自己的头文件.在linux上你有头文件"/usr/include/endian.h"

  • @Tyzoid:不,编译后的程序总是在编译的endian模式下运行,即使处理器能够执行任何一种. (7认同)
  • 我无法相信这一点没有被提高.它不像在编译程序下更改字节顺序,因此从不需要运行时测试. (6认同)
  • 这似乎仅限 Linux:https://man7.org/linux/man-pages/man3/endian.3.html (2认同)

小智 16

嗯......让我感到惊讶的是,没有人意识到编译器会简单地优化测试,并将固定结果作为返回值.这使得上面的所有代码示例都变得无用.唯一可以返回的是编译时的字节序!是的,我测试了上面的所有例子.以下是MSVC 9.0(Visual Studio 2008)的示例.

纯C代码

int32 DNA_GetEndianness(void)
{
    union 
    {
        uint8  c[4];
        uint32 i;
    } u;

    u.i = 0x01020304;

    if (0x04 == u.c[0])
        return DNA_ENDIAN_LITTLE;
    else if (0x01 == u.c[0])
        return DNA_ENDIAN_BIG;
    else
        return DNA_ENDIAN_UNKNOWN;
}
Run Code Online (Sandbox Code Playgroud)

拆卸

PUBLIC  _DNA_GetEndianness
; Function compile flags: /Ogtpy
; File c:\development\dna\source\libraries\dna\endian.c
;   COMDAT _DNA_GetEndianness
_TEXT   SEGMENT
_DNA_GetEndianness PROC                 ; COMDAT

; 11   :     union 
; 12   :     {
; 13   :         uint8  c[4];
; 14   :         uint32 i;
; 15   :     } u;
; 16   : 
; 17   :     u.i = 1;
; 18   : 
; 19   :     if (1 == u.c[0])
; 20   :         return DNA_ENDIAN_LITTLE;

    mov eax, 1

; 21   :     else if (1 == u.c[3])
; 22   :         return DNA_ENDIAN_BIG;
; 23   :     else
; 24   :        return DNA_ENDIAN_UNKNOWN;
; 25   : }

    ret
_DNA_GetEndianness ENDP
END
Run Code Online (Sandbox Code Playgroud)

也许可以关闭这个函数的任何编译时优化,但我不知道.否则,可能可以在装配中对其进行硬编码,尽管这不是便携式的.即便如此,即使这样也可能会得到优化.这让我觉得我需要一些非常糟糕的汇编程序,为所有现有的CPU /指令集实现相同的代码,而且......没关系.

此外,有人在此表示字节序在运行期间不会改变.错误.那里有双端机器.他们的字节顺序可能因执行而异.此外,不仅有Little Endian和Big Endian,还有其他的endianness(简而言之).

我讨厌并喜欢同时编码......

  • 没有big-endian x86处理器这样的东西.即使你在biendian处理器(如ARM或MIPS)上运行Ubuntu,ELF可执行文件也总是大(MSB)或小(LSB)端.不能创建biendian可执行文件,因此不需要运行时检查. (20认同)
  • 你不是必须重新编译才能在不同的平台上运行吗? (11认同)
  • 要关闭此方法中的优化,请使用'volatile union ...'它告诉编译器可以在其他地方更改'u'并且应该加载数据 (4认同)
  • 如果此函数在运行时返回与优化器计算的值不同的值,则意味着优化器出现了错误。您是说有一些已编译的优化二进制代码的示例,可以在不同字节顺序的两种不同体系结构上移植运行,尽管优化器(在整个程序中)在编译期间做出了明显的假设,这些假设似乎与至少其中之一不兼容架构? (3认同)
  • 虽然它适用于MSVC,但它并不适用于所有GCC版本.因此,关键循环内的"运行时检查"可以在编译时正确地解除分支,或者不是.没有100%的保证. (2认同)

sha*_*oth 14

声明一个int变量:

int variable = 0xFF;
Run Code Online (Sandbox Code Playgroud)

现在使用char*指针指向它的各个部分并检查这些部分中的内容.

char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;
Run Code Online (Sandbox Code Playgroud)

根据哪一个指向0xFF字节,您可以检测字节顺序.这需要sizeof(int)> sizeof(char),但对于讨论的平台来说肯定是正确的.


Dav*_*veR 14

我很惊讶没有人提到预处理器默认定义的宏.虽然这些将根据您的平台而有所不同; 它们比编写自己的endian-check要清晰得多.

例如; 如果我们看一下GCC定义的内置宏(在X86-64机器上):

:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1
Run Code Online (Sandbox Code Playgroud)

在PPC机器上,我得到:

:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1
Run Code Online (Sandbox Code Playgroud)

(:| gcc -dM -E -x c -魔法打印出所有内置宏).

  • 这些宏根本不会一致地出现.例如,在Redhat 6 repo的gcc 4.4.5中,运行`echo"\n"| gcc -xc -E -dM - |&grep -i'oreian'`什么都不返回,而Solaris中的gcc 3.4.3(来自`/ usr/sfw/bin`)在这些行中有一个定义.我在VxWorks Tornado(gcc 2.95)-vs- VxWorks Workbench(gcc 3.4.4)上看到了类似的问题. (6认同)

non*_*one 8

有关更多详细信息,您可能需要查看此代码项目文章有关Endianness的基本概念:

如何在运行时动态测试Endian类型?

如计算机动画常见问题解答中所述,您可以使用以下函数来查看您的代码是在Little-还是Big-Endian系统上运行:折叠

#define BIG_ENDIAN      0
#define LITTLE_ENDIAN   1
Run Code Online (Sandbox Code Playgroud)
int TestByteOrder()
{
   short int word = 0x0001;
   char *byte = (char *) &word;
   return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}
Run Code Online (Sandbox Code Playgroud)

此代码将值0001h分配给16位整数.然后指定char指针指向整数值的第一个(最低有效)字节.如果整数的第一个字节是0x01h,那么系统是Little-Endian(0x01h是最低或最不重要的地址).如果是0x00h,则系统为Big-Endian.


Pha*_*rap 8

不要使用union

C++ 不允许通过unions进行类型双关!
从不是最后写入的字段的联合字段读取是未定义的行为
许多编译器支持这样做作为扩展,但语言不保证。

有关更多详细信息,请参阅此答案:

/sf/answers/839787931/


只有两个有效的答案可以保证是可移植的。

如果您有权访问支持 C++20 的系统,第一个答案
std::endian从头<type_traits>文件中使用。

(在撰写本文时,C++20 尚未发布,但除非发生影响std::endian包含的事情,否则从 C++20 开始,这将是在编译时测试字节序的首选方法。)

C++20 以后

constexpr bool is_little_endian = (std::endian::native == std::endian::little);
Run Code Online (Sandbox Code Playgroud)

在 C++20 之前,唯一有效的答案是存储一个整数,然后通过类型双关检查它的第一个字节。
unions的使用不同,这是 C++ 的类型系统明确允许的。

同样重要的是要记住,为了获得最佳的可移植性,static_cast应该使用它,
因为它reinterpret_cast是实现定义的。

如果程序尝试通过以下类型之一以外的泛左值访问对象的存储值,则行为未定义:... acharunsigned char类型。

C++11 以后

enum class endianness
{
    little = 0,
    big = 1,
};

inline endianness get_system_endianness()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01) ? endianness::little : endianness::big;
}
Run Code Online (Sandbox Code Playgroud)

C++11 以后(没有枚举)

inline bool is_system_little_endian()
{
    const int value { 0x01 };
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}
Run Code Online (Sandbox Code Playgroud)

C++98/C++03

inline bool is_system_little_endian()
{
    const int value = 0x01;
    const void * address = static_cast<const void *>(&value);
    const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
    return (*least_significant_address == 0x01);
}
Run Code Online (Sandbox Code Playgroud)

  • 很确定你的代码会在带有 `sizeof (int) == 1` 的目标上失败,这至少在过去是 C++ 所允许的...:D 并不是说​​你需要在那里进行字节序检查。 (2认同)
  • @303这在这里无关紧要,因为_`int`和`char`或`unsigned char`数组确实**不**共享一个共同的初始序列_。 (2认同)

sam*_*moz 6

除非您使用已移植到PPC和Intel处理器的框架,否则您将不得不进行条件编译,因为PPC和Intel平台具有完全不同的硬件架构,管道,总线等.这使得汇编代码在两者之间完全不同.他们俩.

至于查找字节序,请执行以下操作:

short temp = 0x1234;
char* tempChar = (char*)&temp;
Run Code Online (Sandbox Code Playgroud)

您将获得tempChar为0x12或0x34,您将从中知道字节序.

  • 包括`stdint.h`并使用`int16_t`来证明另一个平台上的short是不同的. (7认同)
  • 这依赖于short正好是2个字节,这是不能保证的. (3认同)
  • 尽管基于问题中给出的两种架构,但这是一个相当安全的选择. (3认同)

fuz*_*Tew 6

C++的方法是使用boost,其中预处理器检查和强制转换被分隔在经过深度测试的库中.

Predef库(boost/predef.h)识别四种不同的字节序.

计划将Endian库提交给C++标准,并支持对字节序敏感数据的各种操作.

如上面的答案所述,Endianness将是c ++ 20的一部分.


kat*_*kat 5

如上所述,使用联合技巧.

上面提到的问题几乎没有问题,最值得注意的是,对于大多数架构来说,未对齐的内存访问是非常慢的,并且一些编译器根本不会识别这样的常量谓词,除非字对齐.

因为光端测试很无聊,所以这里有(模板)函数,它将根据你的规范翻转任意整数的输入/输出,而不管主机架构如何.

#include <stdint.h>

#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0

template <typename T>
T endian(T w, uint32_t endian)
{
    // this gets optimized out into if (endian == host_endian) return w;
    union { uint64_t quad; uint32_t islittle; } t;
    t.quad = 1;
    if (t.islittle ^ endian) return w;
    T r = 0;

    // decent compilers will unroll this (gcc)
    // or even convert straight into single bswap (clang)
    for (int i = 0; i < sizeof(r); i++) {
        r <<= 8;
        r |= w & 0xff;
        w >>= 8;
    }
    return r;
};
Run Code Online (Sandbox Code Playgroud)

用法:

要从给定的endian转换为host,请使用:

host = endian(source, endian_of_source)

要从host endian转换为给定的endian,请使用:

output = endian(hostsource, endian_you_want_to_output)

结果代码与在clang上编写手工组件一样快,在gcc上它的速度稍慢(展开&,<<,>>,对于每个字节)但仍然不错.


Pao*_*oli 5

bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}
Run Code Online (Sandbox Code Playgroud)