Есть ли способ сделать утверждение во время компиляции в стиле C ++, чтобы определить порядковый номер машины? - PullRequest
30 голосов
/ 11 ноября 2008

У меня есть некоторый низкоуровневый код сериализации, который является шаблонным, и мне нужно знать порядковый номер системы во время компиляции (поскольку шаблоны специализируются на основе порядкового номера системы).

Прямо сейчас у меня есть заголовок с некоторыми определениями платформы, но я предпочел бы каким-то образом сделать утверждения о порядке байтов с помощью некоторого шаблонного теста (например, static_assert или boost_if). Причина в том, что мой код должен быть скомпилирован и запущен на широком спектре машин, от многих специализированных поставщиков, и, вероятно, устройств, которые не существуют в 2008 году, поэтому я не могу точно догадаться, что может понадобиться в годы заголовка по дороге. А так как база кода имеет ожидаемый срок службы около 10 лет. Так что я не могу всегда следовать коду.

Надеюсь, это прояснит мою ситуацию.

Так кто-нибудь знает о тесте во время компиляции, который может определить порядок байтов, не полагаясь на специфические определения поставщика?

Ответы [ 4 ]

19 голосов
/ 11 ноября 2008

Если вы используете 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;
}
18 голосов
/ 11 ноября 2008

Нет портативного способа сделать это во время компиляции, лучше всего вам использовать Boost макрокоманду байтов или эмулировать методы, которые они используют.

5 голосов
/ 11 ноября 2008

Хм, это интересный вопрос. Держу пари, что это невозможно. Я думаю, что вы должны продолжать использовать макросы и идти с BOOST_STATIC_ASSERT(!BIG_ENDIAN); или static_assert в c ++ 0x. Я думаю, это потому, что endian'nes - это свойство вашей среды исполнения. Однако static_assert рассматривается во время компиляции.

Я предлагаю вам взглянуть на код нового GNU gold ELF линкера. Ян Ланс Тейлор, его автор, использовал шаблоны для выбора правильного порядка байтов во время компиляции, чтобы обеспечить оптимальную производительность во время выполнения. Он явно создает экземпляры всех возможных порядков байтов, так что у него все еще есть отдельная компиляция (не все шаблоны в заголовках) определения и объявления шаблона. У него отличный код.

0 голосов
/ 14 января 2019

Этот ответ основан на следующих спецификациях (это для ясности):

Язык: C ++ v17, 64-битный
Компиляторы: g ++ v8 (Набор инструментов GNU Compiler Collection 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-битное значение в памяти.

На процессоре Little Endian, таком как наборы микросхем Intel и AMD, 16-битное значение сохраняется в виде [low order/least significant byte][high order/most significant byte] (скобки представляют байт в памяти).

На процессоре "Big Endian", таком как наборы микросхем PowerPC, Sun Sparc и IBM S / 390, 16-битное значение сохраняется в виде [high order/most significant byte][low order/least significant byte].

Например, когда мы сохраняем 16-битное (двухбайтовое) значение, скажем, 0x1234, в C ++ uint16_t (тип, определенный в C ++ v11, а затем https://en.cppreference.com/w/cpp/types/integer) переменная размера в Процессор "Little Endian", затем загляните в блок памяти, в котором хранится значение, вы найдете последовательность байтов, [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/q/150167/est-li-sposob-sdelat-utverzhdenie-vo-vremya-kompilyatsii-v-stile-c-chtoby-opredelit-poryadkovyi-nomer-mashinyuestions/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

Я написал эту демонстрацию с помощью инструментария GNU C ++ в Linux Mint, и у меня нет средств для тестирования в других разновидностях C ++, таких как Visual Studio или инструментарий MingW, поэтому я не знаю, что требуется для компиляции в я не имею доступа к Windows на данный момент.

Однако мой друг проверил код с MingW, 64-битный (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>

Когда мы конвертируем 16-битное значение 0x0001 в символ (8-бит) с фрагментом char (0x0001), компилятор использует первое смещение памяти 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

Как видите, мы можем легко определить порядковый номер процессора.


UPDATE:

Я не могу протестировать приведенную выше демонстрацию на процессоре "Big Endian", поэтому я основал код на информации, которую нашел в Интернете. Спасибо М.М за указание на очевидное для меня.

Я обновил демонстрационный код (как показано ниже), чтобы проверить правильность порядка байтов или процессора.

#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.

...