it-roy-ru.com

Обнаружение порядка байтов программно в программе на C++

Есть ли программный способ определить, используете ли вы архитектуру с прямым или обратным порядком байтов? Мне нужно иметь возможность писать код, который будет выполняться в системе Intel или PPC и использовать точно такой же код (т.е. без условной компиляции).

183
Jay T

Мне не нравится метод, основанный на типе punning - его часто предупреждает компилятор. Именно для этого нужны профсоюзы!

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

    return bint.c[0] == 1; 
}

Этот принцип эквивалентен регистру типов, как было предложено другими, но он более ясен - и в соответствии с C99 гарантированно будет правильным. GCC предпочитает это по сравнению с прямым указателем.

Это также намного лучше, чем исправление порядка байтов во время компиляции - для ОС, которые поддерживают мульти-архитектуру (например, толстый бинарный файл на Mac OS X), это будет работать как для ppc/i386, так как в противном случае очень легко все испортить ,.

161
David Cournapeau

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

if ( htonl(47) == 47 ) {
  // Big endian
} else {
  // Little endian.
}

Немного возиться можно было бы быстрее, но этот способ прост, понятен и его практически невозможно испортить.

77
Eric Petroelje

Пожалуйста, смотрите эту статью :

Вот некоторый код, чтобы определить, что такое тип вашей машины

int num = 1;
if(*(char *)&num == 1)
{
    printf("\nLittle-Endian\n");
}
else
{
    printf("Big-Endian\n");
}
57
Andrew Hare

Обычно это делается во время компиляции (особенно по соображениям производительности), используя файлы заголовков, доступные из компилятора, или создавайте свои собственные. В Linux у вас есть заголовочный файл "/usr/include/endian.h"

32
bill

Вы можете использовать std::endian , если у вас есть доступ к компилятору C++ 20, например GCC 8+ или Clang 7+:

#include <type_traits>

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
}
26
Lyberta

Эмм ... Меня удивляет, что никто не понял, что компилятор просто оптимизирует тест и поместит фиксированный результат в качестве возвращаемого значения. Это делает все приведенные выше примеры кода практически бесполезными. Единственное, что будет возвращено - это порядок байтов во время компиляции! И да, я проверил все приведенные выше примеры. Вот пример с MSVC 9.0 (Visual Studio 2008).

Чистый код С

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;
}

Разборка

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

Возможно, возможно отключить ЛЮБУЮ оптимизацию во время компиляции только для этой функции, но я не знаю. В противном случае это может быть возможно жестко закодировать в ассемблере, хотя это не переносимо. И даже тогда даже это может быть оптимизировано. Это заставляет меня думать, что мне нужен какой-то действительно дрянной ассемблер, реализовывать один и тот же код для всех существующих процессоров/наборов команд, и, ну ... неважно.

Кроме того, кто-то здесь сказал, что порядок байтов не меняется во время выполнения. НЕПРАВИЛЬНО. Там есть би-байтовые машины. Их порядок может меняться в процессе исполнения. ТАКЖЕ, есть не только Little Endian и Big Endian, но и другие порядки байтов (что за слово).

Я ненавижу и люблю кодировать одновременно ...

15
Coriiander

Я удивился, что никто не упомянул макросы, которые препроцессор определяет по умолчанию. Хотя они будут варьироваться в зависимости от вашей платформы; они намного чище, чем необходимость написания собственного чека с порядком байтов. 

Например; если мы посмотрим на встроенные макросы, которые определяет GCC (на компьютере X86-64):

:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1

На PPC машине я получаю:

:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1

(Магия :| gcc -dM -E -x c - распечатывает все встроенные макросы).

14
DaveR

Объявите переменную int:

int variable = 0xFF;

Теперь используйте char * указатели на различные его части и проверяйте, что находится в этих частях.

char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;

В зависимости от того, какой из них указывает на байт 0xFF, теперь вы можете определить порядок байтов. Это требует sizeof (int)> sizeof (char), но это определенно верно для обсуждаемых платформ.

14
sharptooth

Для получения более подробной информации, вы можете проверить эту статью codeproject Основные понятия по Endianness :

Как динамически проверять тип Endian во время выполнения?

Как объяснено в Компьютере FAQ по анимации, вы можете использовать Следующая функция, чтобы увидеть, если ваш код работает на Little- или Big-Endian Система: Свернуть

#define BIG_ENDIAN      0
#define LITTLE_ENDIAN   1
int TestByteOrder()
{
   short int Word = 0x0001;
   char *byte = (char *) &Word;
   return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}

Этот код присваивает значение 0001h 16-разрядное целое число Указатель на символ тогда назначен на точку на первом (наименее значимый) байт целочисленное значение. Если первый байт целое число 0x01h, затем система является Little-Endian (0x01h находится в самом низком или наименее значимом адресе). Если это 0x00h, тогда система является Big-Endian.

7
none

Путь C++ заключался в использовании boost , где проверки и приведение препроцессора разделены внутри очень тщательно протестированных библиотек.

Библиотека Predef (boost/prefn.h) распознает четыре различных вида байтов .

Endian Library планировалось передать в стандарт C++ и поддерживает широкий спектр операций с данными, чувствительными к порядку байтов.

Как указано в ответах выше, Endianness будет частью c ++ 20.

6
fuzzyTew

Как указано выше, используйте трюки союза.

Тем не менее, есть несколько проблем с теми, о которых говорилось выше, в особенности то, что доступ к невыровненной памяти общеизвестно медленен для большинства архитектур, и некоторые компиляторы даже не распознают такие постоянные предикаты, если только Word не выровнен.

Поскольку простой порядок байтов является скучным, здесь идет функция (шаблон), которая переворачивает ввод/вывод произвольного целого числа в соответствии с вашей спецификацией, независимо от архитектуры хоста.

#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;
};

Использование:

Для преобразования из данного порядкового номера в Host используйте:

Host = endian(source, endian_of_source)

Чтобы преобразовать из Endian хоста в данный Endian, используйте:

output = endian(hostsource, endian_you_want_to_output)

Результирующий код работает так же быстро, как и написание вручную ассемблера на clang, на gcc он немного медленнее (развернутый &, <<, >>, | для каждого байта), но все еще приличный.

6
kat

Если вы не используете инфраструктуру, которая была портирована на PPC и процессоры Intel, вам придется выполнять условные компиляции, поскольку платформы PPC и Intel имеют совершенно разные аппаратные архитектуры, конвейеры, шины и т.д. Это делает код ассемблера совершенно другим между ними.

Что касается нахождения порядка байтов, сделайте следующее:

short temp = 0x1234;
char* tempChar = (char*)&temp;

Вы также получите tempChar равным 0x12 или 0x34, из которого вы будете знать порядок байтов.

5
samoz
bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}
4
Paolo Brandoli

время компиляции, не-макрос, C++ 11 решение constexpr:

union {
  uint16_t s;
  unsigned char c[2];
} constexpr static  d {1};

constexpr bool is_little_endian() {
  return d.c[0] == 1;
}
4
zhaorufei

Я бы сделал что-то вроде этого:

bool isBigEndian() {
    static unsigned long x(1);
    static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
    return result;
}

Вдоль этих строк вы получите эффективную по времени функцию, которая выполняет вычисления только один раз. 

4
Jeremy Mayhew

не проверено, но на мой взгляд, это должно работать? потому что это будет 0x01 на младшем порядке, и 0x00 на старшем порядке?

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
3
hanshenrik
union {
    int i;
    char c[sizeof(int)];
} x;
x.i = 1;
if(x.c[0] == 1)
    printf("little-endian\n");
else    printf("big-endian\n");

Это еще одно решение. Аналогично решению Эндрю Хэра.

3
Neeraj

Вы также можете сделать это через препроцессор, используя что-то вроде файла заголовка boost, который можно найти boost endian

2
nmushell

Если заголовок с порядком байтов не только для GCC, он предоставляет макросы, которые вы можете использовать.

#include "endian.h"
...
if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... }
else if (__BYTE_ORDER == __BIG_ENDIAN) { ... }
else { throw std::runtime_error("Sorry, this version does not support PDP Endian!");
...
1
Mark A. Libby

Если вам не нужна условная компиляция, вы можете просто написать независимый код с порядком байтов. Вот пример (взят из Роб Пайк ):

Чтение целого числа, хранящегося в порядке с прямым порядком байтов на диске, с прямым порядком байтов:

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

Тот же код, пытающийся учесть машинный порядок байтов:

i = *((int*)data);
#ifdef BIG_ENDIAN
/* swap the bytes */
i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0);
#endif
1
fjardon
int i=1;
char *c=(char*)&i;
bool littleendian=c;
1
Jon Bright

Как насчет этого?

#include <cstdio>

int main()
{
    unsigned int n = 1;
    char *p = 0;

    p = (char*)&n;
    if (*p == 1)
        std::printf("Little Endian\n");
    else 
        if (*(p + sizeof(int) - 1) == 1)
            std::printf("Big Endian\n");
        else
            std::printf("What the crap?\n");
    return 0;
}
1
Abhay

То, как компиляторы C (по крайней мере, все, кого я знаю) работают с порядком байтов имеет должно быть решено во время компиляции. Даже для biendian процессоров (например, ARM или MIPS) вы должны выбирать порядковый номер во время компиляции. Более того, порядок байтов определяется во всех распространенных форматах файлов для исполняемых файлов (таких как ELF). Несмотря на то, что можно создать двоичный двоичный код двоичного кода (может быть, для некоторого ARM серверного эксплойта?), Это, вероятно, должно быть сделано в Assembly.

0
Fabel

Смотрите Endianness - Иллюстрация кода уровня C.

// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };


ENDIANESS CheckArchEndianalityV1( void )
{
    int Endian = 0x00000001; // assuming target architecture is 32-bit    

    // as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least     Significant Byte) = 0x01
    // casting down to a single byte value LSB discarding higher bytes    

    return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN;
} 
0
gimel

Не используйте union!

C++ не разрешает наказание типов через unions!
Чтение из поля объединения, которое не было последним записанным полем, является неопределенное поведение!
Многие компиляторы поддерживают это как расширения, но язык не дает никаких гарантий.

Смотрите этот ответ для более подробной информации:

https://stackoverflow.com/a/11996970


Есть только два правильных ответа, которые гарантированно будут переносимыми.

Первый ответ, если у вас есть доступ к системе, которая поддерживает 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);

До C++ 20 единственный верный ответ - сохранить целое число, а затем проверить его первый байт с помощью типа punning.
В отличие от использования unions, это явно разрешено системой типов C++.

Также важно помнить, что для оптимальной переносимости следует использовать static_cast,
потому что reinterpret_cast определяется реализацией.

Если программа пытается получить доступ к сохраненному значению объекта через glvalue другого, чем один из следующих типов, поведение не определено: ... тип char или unsigned 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;
}

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);
}

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);
}
0
Pharap

Вот еще одна версия C. Он определяет макрос с именем wicked_cast() для вставки строкового типа через литералы объединения C99 и нестандартный оператор __typeof__.

#include <limits.h>

#if UCHAR_MAX == UINT_MAX
#error endianness irrelevant as sizeof(int) == 1
#endif

#define wicked_cast(TYPE, VALUE) \
    (((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest)

_Bool is_little_endian(void)
{
    return wicked_cast(unsigned char, 1u);
}

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

0
Christoph

хотя не существует быстрого и стандартного способа определить его, он выведет его:

#include <stdio.h> 
int main()  
{ 
   unsigned int i = 1; 
   char *c = (char*)&i; 
   if (*c)     
       printf("Little endian"); 
   else
       printf("Big endian"); 
   getchar(); 
   return 0; 
} 
0
yekanchi