Вопрос по c++, endianness, algorithm – Обнаружение порядка байтов программно в программе на C ++

188

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

AFAIK, нет надежного и универсального способа сделать это.gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html user48956
Почему бы не определить порядок байтов во время компиляции? Возможно, он не изменится во время выполнения. ephemient
Для полноты, вот ссылка на чей-то вопрос о попытке измерения порядка байтов (во время компиляции):stackoverflow.com/questions/280162/… Faisal Vali

Ваш Ответ

28   ответов
0

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

15

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

Pure C code

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

Disassembly

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, но и другие порядковые номера (что за слово).

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

Разве вы не должны перекомпилировать для запуска на другой платформе в любом случае?
Не существует такого понятия, как процессор x86 с прямым порядком байтов. Даже если вы запускаете Ubuntu на двухпроцессорном процессоре (например, ARM или MIPS), исполняемые файлы ELF всегда имеют большой (MSB) или маленький (LSB) порядковый номер. Невозможно создать исполняемые файлы Biendian, поэтому проверки во время выполнения не требуются.
Чтобы отключить оптимизацию в этом методе, используйте «volatile union ...» Он сообщает компилятору, что «u»; можно изменить где-то еще, и данные должны быть загружены
Хотя он хорошо работает для MSVC, он не подходит для всех версий GCC при любых обстоятельствах. Следовательно, «проверка во время выполнения» внутри критического цикла может быть правильно неразветвленным во время компиляции, или нет. Нет 100% гарантии.
Если эта функция возвращает другое значение во время выполнения, чем рассчитывает оптимизатор, это означает, что оптимизатор содержит ошибки. Вы говорите, что есть примеры скомпилированного оптимизированного бинарного кода, который может работать на двух разных архитектурах с разным порядком байтов, несмотря на очевидные предположения, сделанные оптимизатором (во всей программе) во время компиляции, которые могут показаться несовместимыми хотя бы с одним из них? архитектуры?
6

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

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

#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 = endian(source, endian_of_source)

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

output = endian(hostsource, endian_you_want_to_output)

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

33

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

@Tyzoid: Нет, скомпилированная программа всегда будет работать в том порядке, в котором она была скомпилирована, даже если процессор способен на это.
@ Dolda2000 Возможно, вы могли видеть режимы байтов ARM.
Я не могу поверить, что за это не проголосовали выше. Это не означает, что порядковый номер будет изменяться в рамках скомпилированной программы, поэтому никогда не требуется тест во время выполнения.
1
int i=1;
char *c=(char*)&i;
bool littleendian=c;
5

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

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

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

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

Тем не менее, это будет довольно безопасная ставка, основанная на двух архитектурах, приведенных в вопросе.
Это основано на том, что short составляет ровно 2 байта, что не гарантируется.
Включаютstdint.h и использоватьint16_t будущим доказательствам против короткого отличия на другой платформе.
2

используя что-то вроде файла заголовка Boost, который можно найтиBoost Endian

7

вы можете проверить эту статью codeprojectОсновные понятия по порядку байтов:

How to dynamically test for the Endian type at run time?

As explained in Computer Animation FAQ, you can use the following function to see if your code is running on a Little- or Big-Endian system: Collapse

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

This code assigns the value 0001h to a 16-bit integer. A char pointer is then assigned to point at the first (least-significant) byte of the integer value. If the first byte of the integer is 0x01h, then the system is Little-Endian (the 0x01h is in the lowest, or least-significant, address). If it is 0x00h then the system is Big-Endian.

4

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

constexpr bool is_little_endian() {
  return d.c[0] == 1;
}
0 время выполнения ... мне нравится!
Разве это не UB в C ++?
это не законно в контексте constexpr. Вы не можете получить доступ к члену союза, который не был инициализирован напрямую. Не существует способа легально определить порядок байтов во время компиляции без магии препроцессора.
Я полагаю, это обнаруживает неконцентрацию машины сборки, а не цель?
Есть ли конкретная причина, по которой вы использовали unsigned char вместо uint8_t?
1

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

#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!");
...
Это не__BYTE_ORDER__, __ORDER_LITTLE_ENDIAN__ а также__ORDER_BIG_ENDIAN__?
1

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

основанный на типе 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, так как в противном случае очень легко все испортить ,

Вы уверены, что это хорошо определено? В C ++ только один член объединения может быть активным одновременно - то есть вы не можете назначать, используя одно имя члена, и читать, используя другое (хотя есть исключение для структур, совместимых с макетом)
@Matt: я посмотрел в Google, и bint, кажется, имеет значение на английском языке, о котором я не знал :)
Я не рекомендую называть переменную "bint" :)
Боже, благослови GCC & # x2122 ;.
Я проверял это, и как в gcc 4.0.1, так и в gcc 4.4.1 результат этой функции может быть определен во время компиляции и рассматриваться как константа. Это означает, что компилятор отключится, если ветви будут зависеть исключительно от результата этой функции и никогда не будут приняты на рассматриваемой платформе. Скорее всего, это не так для многих реализаций htonl.
0

он выведет его:

#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

Порядок байтов - Иллюстрация кода уровня С.

// 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

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

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

3
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

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

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
79

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

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

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

@sharptooth - медленный - относительный термин, но да, если скорость действительно является проблемой, используйте ее один раз в начале программы и установите глобальную переменную с порядком байтов.
Обратите внимание, что в Linux (gcc) htonl подвергается постоянному сворачиванию во время компиляции, поэтому выражение этой формы вообще не имеет накладных расходов времени выполнения (т. Е. Оно постоянно сворачивается в 1 или 0, а затем удаление мертвого кода удаляет другая ветка если)
Операции преобразования сети также можно использовать для преобразования всего в порядок с прямым порядком байтов, что позволяет решить другие проблемы, с которыми может столкнуться Джей.
Кроме того, в x86 htonl может быть реализован (и в Linux / gcc) очень эффективно с использованием встроенного ассемблера, особенно если вы нацелены на микроархитектуру с поддержкойBSWAP операция.
У htonl есть еще одна проблема: на некоторых платформах (windows?) он находится не в самой библиотеке времени выполнения C, а в дополнительных сетевых библиотеках (сокет и т. д.). Это довольно серьезная помеха только для одной функции, если в противном случае вам не нужна библиотека.
-1

Computer System: a programmer's perspective, и есть проблема, чтобы определить, какой это порядковый номер в C-программе.

Я использовал функцию указателя, чтобы сделать это следующим образом:

#include <stdio.h>

int main(void){
    int i=1;
    unsigned char* ii = &i;

    printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big"));
    return 0;
}

Какint занимает 4 байта, иchar занимает всего 1 байт. Мы могли бы использоватьchar pointer указать наint со значением 1. Таким образом, если компьютер имеет порядок байтовchar тотchar pointer указывает на значение со значением 1, в противном случае его значение должно быть 0.

^ если вы хотите придираться, лучше всего это int16_fast_t. и текущий код @ Archimedes520 не будет работать на арке, где int изначально является int8;) (хотя, во-первых, это может идти вразрез со стандартами c)
это было бы улучшено с помощью int32t.
4
bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}
Будет ли это эквивалентно?#define IS_BIGENDIAN() (*((char*) &((int){ 0x00ff })) == (0x00))
1

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

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

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

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

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

#include <stdint.h>

int* _BE = 0;

int is_big_endian() {
    if (_BE == 0) {
        uint16_t* teste = (uint16_t*)malloc(4);
        *teste = (*teste & 0x01FE) | 0x0100;
        uint8_t teste2 = ((uint8_t*) teste)[0];
        free(teste);
        _BE = (int*)malloc(sizeof(int));
        *_BE = (0x01 == teste2);
    }
    return *_BE;
}

MinGW не смог оптимизировать этот код, хотя он и здесь оптимизирует другие коды. Я полагаю, что это потому, что я оставляю «случайным» значение, которое было распределено в меньшей байтовой памяти, как было (по меньшей мере, 7 из его битов), так что компилятор не может знать, что это случайное значение, и он не оптимизирует функцию.

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

Это ужасно ...
Зачем выделять 4 байта для работы с 2-байтовым значением? Зачем маскировать неопределенное значение с0x7FE? Зачем использоватьmalloc() совсем? это расточительно. А также_BE Это (хотя и небольшая) утечка памяти и состояние гонки, ожидающее наступления, преимущества динамического кэширования результата не стоят проблем. Я бы сделал что-то более похожее на это:static const uint16_t teste = 1; int is_little_endian() { return (0x01 == ((uint8_t*)&teste)[0]); } int is_big_endian() { return (0x01 == ((uint8_t*)&teste)[1]); } Простой и эффективный, и гораздо меньше работы для выполнения во время выполнения.
14

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

int variable = 0xFF;

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

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

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

14

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

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

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

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

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

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

Эти макросы не отображаются последовательно вообще. Например, в gcc 4.4.5 из репозитория Redhat 6 работаетecho "\n" | gcc -x c -E -dM - |& grep -i 'endian' ничего не возвращает, тогда как gcc 3.4.3 (из/usr/sfw/bin во всяком случае) в солярисе есть определение по этим направлениям. Я видел аналогичные проблемы на VxWorks Tornado (gcc 2.95) -vs- VxWorks Workbench (gcc 3.4.4).
58

Эта статья:

Here is some code to determine what is the type of your machine

int num = 1;
if(*(char *)&num == 1)
{
    printf("\nLittle-Endian\n");
}
else
{
    printf("Big-Endian\n");
}
почему ЭТОТ ответ в значительной степени ЕДИНСТВЕННЫЙ ОТВЕТ, который НЕ заставляет меня думать "чувак, что ты делаешь?", как в случае большинства ответов здесь: o
Имейте в виду, что это зависит от разной длины int и char, что почти всегда так, но не гарантируется.
Я работал над встроенными системами, в которых были короткие int и char одинакового размера ... Я не могу вспомнить, был ли обычный int такого же размера (2 байта) или нет.
это проще, чем хтонл вещи .. нам не нужна библиотека .. круто
@Shillard int должен быть по крайней мере таким большим, но в стандарте нет требования, чтобы char был ограничен меньшим! Если вы посмотрите на семейство TI F280x, вы обнаружите, что CHAR_BIT равен 16 и sizeof (int) == sizeof (char), в то время как указанные вами ограничения сохраняются абсолютно нормально ...
4

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

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

Вы можете включить это? не уверен, что встроенные вызовут несколько блоков памяти статических переменных
27

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
}
@ Xeverous Для этого требуются только перечисления в определенных областях, поэтому я подозреваю, что большинство поставщиков добавят его в свою реализацию stdlib как одно из своих предыдущих изменений.
Как и у всех, у меня есть доступ к черновикам / предложениям C ++ 17 и 20, но существует ли когда-либо какой-либо компилятор C ++ 20?
@ Xeverous GCC 8 был выпущен и поддерживает его.
Это можно изменить наif constexpr
@ Xeverous да, добавил.
6

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

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

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

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

исправил и сделал вики
К вашему сведению, «четыре различных вида байтов» ссылка не работает,

Похожие вопросы