有没有办法做一个C ++样式的编译时断言来确定机器的字节序?

时间:2008-11-11 06:29:59

标签: c++ templates metaprogramming endianness

我有一些模板化的低级序列化代码,我需要在编译时明确知道系统的字节顺序(因为模板专门根据系统的字节顺序)。

现在我有一个带有一些平台定义的标题,但我宁愿通过一些模板化测试(比如static_assert或boost_if)来做关于字节序的断言。原因是我的代码需要编译并在许多专业供应商的各种机器上运行,并且可能是2008年不存在的设备,因此我无法猜测可能需要进入标题年份的内容在路上。而且由于代码库的预期寿命约为10年。所以我无法永远遵循代码。

希望这能使我的情况变得清晰。

那么有没有人知道可以确定字节序的编译时测试,而不依赖于供应商特定的定义?

4 个答案:

答案 0 :(得分:19)

如果你正在使用autoconf,你可以使用AC_C_BIGENDIAN宏,这是相当有效的(默认情况下设置WORDS_BIGENDIAN定义)

或者,您可以尝试类似以下内容(取自autoconf)以获得可能已经优化的测试(GCC,至少,删除其他分支)

int is_big_endian()
{
    union {
        long int l;
        char c[sizeof (long int)];
    } u;

    u.l = 1;

    if (u.c[sizeof(long int)-1] == 1)
    {
        return 1;
    }
    else
        return 0;
}

答案 1 :(得分:18)

在编译时没有可移植的方法,最好的办法是使用Boost endian macros或模仿他们使用的方法。

答案 2 :(得分:5)

嗯,这是一个有趣的问题。我敢打赌,这是不可能的。我认为你必须继续使用宏,并在c ++ 0x中使用BOOST_STATIC_ASSERT(!BIG_ENDIAN);static_assert。我认为这是因为如果你的执行环境,endian'nes是一个属性。但是,在编译时考虑static_assert。

我建议您查看新的 GNU gold ELF链接器的代码。其作者Ian Lance Taylor使用模板在编译时选择正确的字节序,以确保在运行时获得最佳性能。他明确地实例化了所有可能的endians,因此他仍然有模板定义和声明的单独编译(不是标题中的所有模板)。他的代码很棒。

答案 3 :(得分:0)

此答案基于以下规格(为清楚起见):

  

语言:C ++ v17,64位
  编译器:g ++ v8(GNU编译器集合https://www.gnu.org/software/gcc/)和MingW 8.1.0工具链(https://sourceforge.net/projects/mingw-w64/files/
  操作系统:Linux Mint和Windows

以下两行代码可用于成功检测处理器的字节序:

const uint8_t IsLittleEndian = char (0x0001);

#define IsLittleEndian char (0x0001)

这两个神奇的小语句可以利用处理器如何在内存中存储16位值。

在“小尾数”处理器(如Intel和AMD芯片组)上,以[low order/least significant byte][high order/most significant byte]的方式存储16位值(括号表示内存中的一个字节)。

在PowerPC,Sun Sparc和IBM S / 390芯片组等“大端”处理器上,以[high order/most significant byte][low order/least significant byte]的方式存储16位值。

例如,当我们将16位(两个字节)的值存储到C ++ 0x1234(在C ++ v11和更高版本的https://en.cppreference.com/w/cpp/types/integer中定义的类型)中时,假设uint16_t在“ Little Endian”处理器上使用size变量,然后将其值存储到内存块中,您会发现字节序列[34][12]

在“ Big Endian处理器”上,0x1234的值存储为[12][34]

这里有一个小示例,可以帮助演示如何在大小端的处理器上存储各种大小的C ++整数变量:

#define __STDC_FORMAT_MACROS // Required for the MingW toolchain
#include <iostream>
#include <inttypes.h>

const uint8_t IsLittleEndian = char (0x0001);
//#define IsLittleEndian char (0x0001)

std::string CurrentEndianMsg;
std::string OppositeEndianMsg;

template <typename IntegerType>
void PrintIntegerDetails(IntegerType IntegerValue)
{
    uint16_t SizeOfIntegerValue = sizeof(IntegerValue);
    int8_t i;

    std::cout << "Integer size (in bytes): " << SizeOfIntegerValue << "\n";
    std::cout << "Integer value (Decimal): " << IntegerValue << "\n";
    std::cout << "Integer value (Hexidecimal): ";

    switch (SizeOfIntegerValue)
    {
        case 2: printf("0x%04X\n", (unsigned int) IntegerValue);
                break;
        case 4: printf("0x%08X\n", (unsigned int) IntegerValue);
                break;
        case 8: printf("0x%016" PRIX64 "\n", (uint64_t) IntegerValue);
                break;
    }

    std::cout << "Integer stored in memory in byte order:\n";
    std::cout << "        " << CurrentEndianMsg << " processor [current]: ";

    for(i = 0; i < SizeOfIntegerValue; i++)https://stackoverflow.com/qhttps://stackoverflow.com/questions/280162/is-there-a-way-to-do-a-c-style-compile-time-assertion-to-determine-machines-e/54175491#54175491uestions/280162/is-there-a-way-to-do-a-c-style-compile-time-assertion-to-determine-machines-e/54175491#54175491
    {
        printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
    }

    std::cout << "\n        " << OppositeEndianMsg << " processor  [simulated]: ";

    for(i = SizeOfIntegerValue - 1; i >= 0; i--)
    {
        printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
    }

    std::cout << "\n\n";
}


int main()
{
    uint16_t ValueUInt16a = 0x0001;
    uint16_t ValueUInt16b = 0x1234;
    uint32_t ValueUInt32a = 0x00000001;
    uint32_t ValueUInt32b = 0x12345678;
    uint64_t ValueUInt64a = 0x0000000000000001;
    uint64_t ValueUInt64b = 0x123456789ABCDEF0;

    std::cout << "Current processor endianness: ";

    switch (IsLittleEndian) {
        case 0: CurrentEndianMsg = "Big Endian";
                OppositeEndianMsg = "Little Endian";
                break;
        case 1: CurrentEndianMsg = "Little Endian";
                OppositeEndianMsg = "Big Endian";
                break;
    }

    std::cout << CurrentEndianMsg << "\n\n";

    PrintIntegerDetails(ValueUInt16a);
    PrintIntegerDetails(ValueUInt16b);
    PrintIntegerDetails(ValueUInt32a);
    PrintIntegerDetails(ValueUInt32b);
    PrintIntegerDetails(ValueUInt64a);
    PrintIntegerDetails(ValueUInt64b);

    return 0;
}

这是我机器上的演示输出:

Current processor endianness: Little Endian

Integer size (in bytes): 2
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x0001
Integer stored in memory in byte order:
        Little Endian processor [current]: 01 00
        Big Endian processor  [simulated]: 00 01

Integer size (in bytes): 2
Integer value (Decinal): 4660
Integer value (Hexidecimal): 0x1234
Integer stored in memory in byte order:
        Little Endian processor [current]: 34 12
        Big Endian processor  [simulated]: 12 34

Integer size (in bytes): 4
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x00000001
Integer stored in memory in byte order:
        Little Endian processor [current]: 01 00 00 00
        Big Endian processor  [simulated]: 00 00 00 01

Integer size (in bytes): 4
Integer value (Decinal): 305419896
Integer value (Hexidecimal): 0x12345678
Integer stored in memory in byte order:
        Little Endian processor [current]: 78 56 34 12
        Big Endian processor  [simulated]: 12 34 56 78

Integer size (in bytes): 8
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x0000000000000001
Integer stored in memory in byte order:
        Little Endian processor [current]: 01 00 00 00 00 00 00 00
        Big Endian processor  [simulated]: 00 00 00 00 00 00 00 01

Integer size (in bytes): 8
Integer value (Decinal): 13117684467463790320
Integer value (Hexidecimal): 0x123456789ABCDEF0While the process
Integer stored in memory in byte order:
        Little Endian processor [current]: F0 DE BC 9A 78 56 34 12
        Big Endian processor  [simulated]: 12 34 56 78 9A BC DE F0

我使用Linux Mint中的GNU C ++工具链编写了此演示,并且没有能够在其他类型的C ++(如Visual Studio或MingW工具链)中进行测试的方法,所以我不知道要在Windows中进行编译需要什么?他们,目前我也无法访问Windows。

但是,我的一个朋友使用64位MingW(x86_64-8.1.0-release-win32-seh-rt_v6-rev0)测试了该代码,但出现错误。经过一番研究,我发现我需要在代码顶部添加行#define __STDC_FORMAT_MACROS以便使用MingW进行编译。

现在,我们可以直观地看到如何将16位值存储在内存中,让我们看看如何利用该值来确定处理器的字节序。

要在可视化内存中存储16位值的方式方面提供一些额外帮助,让我们看一下以下图表:

16-Bit Value (Hex):  0x1234

Memory Offset:       [00] [01]
                     ---------
Memory Byte Values:  [34] [12]  <Little Endian>
                     [12] [34]  <Big Endian>

================================================

16-Bit Value (Hex):  0x0001

Memory Offset:       [00] [01]
                     ---------
Memory Byte Values:  [01] [00]  <Little Endian>
                     [00] [01]  <Big Endian>

当我们使用代码段0x0001将16位值char (0x0001)转换为char(8位)时,编译器将16位值的第一个内存偏移量用作新值。这是另一个图表,显示“ Little Endian”和“ Big Endian”处理器上会发生什么:

Original 16-Bit Value: 0x0001

Stored in memory as: [01][00]  <-- Little Endian
                     [00][01]  <-- Big Endian

Truncate to char:    [01][xx]  <-- Little Endian
                     [01]      Final Result
                     [00][xx]  <-- Big Endian
                     [00]      Final Result

如您所见,我们可以轻松确定处理器的字节序。


更新

我无法在“ Big Endian”处理器上测试上面的演示,因此我将代码基于在网上找到的信息。感谢M.M为我指出显而易见的地方。

我已经更新了演示代码(如下所示)以测试字节序或处理器。

#define __STDC_FORMAT_MACROS // Required for the MingW toolchain
#include <iostream>
#include <inttypes.h>

std::string CurrentEndianMsg;
std::string OppositeEndianMsg;

template <typename IntegerType>
void PrintIntegerDetails(IntegerType IntegerValue)
{
    uint16_t SizeOfIntegerValue = sizeof(IntegerValue);
    int8_t i;

    std::cout << "Integer size (in bytes): " << SizeOfIntegerValue << "\n";
    std::cout << "Integer value (Decimal): " << IntegerValue << "\n";
    std::cout << "Integer value (Hexidecimal): ";

    switch (SizeOfIntegerValue)
    {
        case 2: printf("0x%04X\n", (unsigned int) IntegerValue);
                break;
        case 4: printf("0x%08X\n", (unsigned int) IntegerValue);
                break;
        case 8: printf("0x%016" PRIX64 "\n", (uint64_t) IntegerValue);
                break;
    }

    std::cout << "Integer stored in memory in byte order:\n";
    std::cout << "        " << CurrentEndianMsg << " processor [current]: ";

    for(i = 0; i < SizeOfIntegerValue; i++)
    {
        printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
    }

    std::cout << "\n        " << OppositeEndianMsg << " processor  [simulated]: ";

    for(i = SizeOfIntegerValue - 1; i >= 0; i--)
    {
        printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
    }

    std::cout << "\n\n";
}


int main()
{
    uint16_t ValueUInt16a = 0x0001;
    uint16_t ValueUInt16b = 0x1234;
    uint32_t ValueUInt32a = 0x00000001;
    uint32_t ValueUInt32b = 0x12345678;
    uint64_t ValueUInt64a = 0x0000000000000001;
    uint64_t ValueUInt64b = 0x123456789ABCDEF0;

    uint16_t EndianTestValue = 0x0001;
    uint8_t IsLittleEndian = ((unsigned char*) &EndianTestValue)[0];

    std::cout << "Current processor endianness: ";

    switch (IsLittleEndian) {
        case 0: CurrentEndianMsg = "Big Endian";
                OppositeEndianMsg = "Little Endian";
                break;
        case 1: CurrentEndianMsg = "Little Endian";
                OppositeEndianMsg = "Big Endian";
                break;
    }

    std::cout << CurrentEndianMsg << "\n\n";

    PrintIntegerDetails(ValueUInt16a);
    PrintIntegerDetails(ValueUInt16b);
    PrintIntegerDetails(ValueUInt32a);
    PrintIntegerDetails(ValueUInt32b);
    PrintIntegerDetails(ValueUInt64a);
    PrintIntegerDetails(ValueUInt64b);

    return 0;
}

此更新的演示创建了一个16位值0x0001,然后读取了变量存储器中的第一个字节。从上面的输出中可以看到,在“ Little Endian”处理器上,该值为0x01。在“ Big Endian”处理器上,该值为0x00。