C Определение макроса для определения машины с прямым или обратным порядком байтов?

107

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

unsigned char test_endian( void )
{
    int test_var = 1;
    unsigned char *test_endian = (unsigned char*)&test_var;

    return (test_endian[0] == 0);
}
манав мн
источник
2
Почему бы не включить тот же код в макрос?
Sharptooth
4
Вы не можете переносимо определить порядок байтов только с помощью препроцессора C. Вы также хотите 0вместо этого NULLв своем последнем тесте и заменить один из test_endianобъектов на что-то другое :-).
Алок Сингхал,
2
Также зачем нужен макрос? Встроенная функция будет делать то же самое и намного безопаснее.
Sharptooth
13
@Sharptooth, макрос привлекателен, потому что его значение может быть известно во время компиляции, что означает, что вы можете использовать порядок байтов вашей платформы для управления созданием экземпляров шаблона, например, или, возможно, даже выбрать различные блоки кода с помощью #ifдирективы.
Роб Кеннеди,
3
Это правда, но неэффективно. Если у меня есть ЦП с прямым порядком байтов, и я пишу данные с прямым порядком байтов в сеть или в файл, я бы предпочел избегать распаковки и переупаковки данных без цели. Раньше я писал видеодрайверы, чтобы заработать себе на жизнь. Это чрезвычайно важно при написании пикселей к видеокарте , чтобы оптимизировать каждое место , вы можете.
Эдвард Фальк

Ответы:

102

Код, поддерживающий произвольный порядок байтов, готовый для помещения в файл с именем order32.h:

#ifndef ORDER32_H
#define ORDER32_H

#include <limits.h>
#include <stdint.h>

#if CHAR_BIT != 8
#error "unsupported char size"
#endif

enum
{
    O32_LITTLE_ENDIAN = 0x03020100ul,
    O32_BIG_ENDIAN = 0x00010203ul,
    O32_PDP_ENDIAN = 0x01000302ul,      /* DEC PDP-11 (aka ENDIAN_LITTLE_WORD) */
    O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 (aka ENDIAN_BIG_WORD) */
};

static const union { unsigned char bytes[4]; uint32_t value; } o32_host_order =
    { { 0, 1, 2, 3 } };

#define O32_HOST_ORDER (o32_host_order.value)

#endif

Вы можете проверить системы с прямым порядком байтов через

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Кристоф
источник
11
Однако это не позволяет вам определять порядок байтов до времени выполнения. Следующее не может быть скомпилировано, потому что. / ** isLittleEndian :: result -> 0 или 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
Неужели невозможно получить результат до времени выполнения?
k06a
8
Почему char? Лучше использовать uint8_tи потерпеть неудачу, если этот тип недоступен (что можно проверить #if UINT8_MAX). Обратите внимание, что CHAR_BITэто не зависит от uint8_t.
Андреас Шпиндлер,
2
Это UB на c ++: stackoverflow.com/questions/11373203/…
Lyberta 01
3
Для полноты картины позвольте мне добавить еще один:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Эдвард Фальк
49

Если у вас есть компилятор, поддерживающий составные литералы C99:

#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})

или:

#define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c)

В общем, вы должны попытаться написать код, который не зависит от порядка байтов хост-платформы.


Пример независимой от хоста реализации ntohl():

uint32_t ntohl(uint32_t n)
{
    unsigned char *np = (unsigned char *)&n;

    return ((uint32_t)np[0] << 24) |
        ((uint32_t)np[1] << 16) |
        ((uint32_t)np[2] << 8) |
        (uint32_t)np[3];
}
кафе
источник
3
«вам следует попытаться написать код, который не зависит от порядка байтов платформы хоста». К сожалению, моя просьба: «Я знаю, что мы пишем уровень совместимости с POSIX, но я не хочу реализовывать ntoh, потому что это зависит от порядка байтов на платформе хоста» всегда оставалась без внимания ;-). Другой главный кандидат, который я видел, - это обработка графического формата и код преобразования - вы не хотите, чтобы все основывалось на постоянном вызове ntohl.
Стив Джессоп,
5
Вы можете реализовать ntohlэто способом, который не зависит от порядка байтов хост-платформы.
caf,
1
@caf, как бы вы написали ntohl независимым от хоста способом?
Hayri Uğur Koltuk
3
@AliVeli: Я добавил к ответу пример реализации.
caf
6
Я также должен добавить для записи, что "(* (uint16_t *)" \ 0 \ xff "<0x100)" не будет компилироваться в константу, независимо от того, насколько я оптимизирую, по крайней мере, с gcc 4.5.2. Он всегда создает исполняемый код.
Эдвард Фальк
43

Не существует стандарта, но во многих системах, в том числе <endian.h>, вы найдете несколько определений, которые нужно искать.

Игнасио Васкес-Абрамс
источник
30
Проверьте порядок байтов с помощью #if __BYTE_ORDER == __LITTLE_ENDIANи #elif __BYTE_ORDER == __BIG_ENDIAN. И сгенерируйте #errorиначе.
To1ne
6
<endian.h>недоступен в Windows
rustyx 02
2
В проектах Android и Chromium используется, endian.hесли не определено __APPLE__или _WIN32.
patryk.beza
1
В OpenBSD 6.3 <endian.h> предоставляет #if BYTE_ORDER == LITTLE_ENDIAN(или BIG_ENDIAN) без символов подчеркивания перед именами. _BYTE_ORDERтолько для системных заголовков. __BYTE_ORDERне существует.
Джордж Кёлер
@ To1ne Я сомневаюсь, что порядок байтов актуален для Windows, поскольку Windows (по крайней мере, в настоящее время) работает только на машинах x86 и ARM. x86 всегда является LE, а ARM можно настроить для использования любой архитектуры.
SimonC
27

Чтобы определить порядок байтов во время выполнения, вы должны иметь возможность обращаться к памяти. Если вы придерживаетесь стандарта C, для объявления переменной в памяти требуется оператор, а для возврата значения требуется выражение. Я не знаю, как это сделать в одном макросе - вот почему у gcc есть расширения :-)

Если вы хотите иметь файл .h, вы можете определить

static uint32_t endianness = 0xdeadbeef; 
enum endianness { BIG, LITTLE };

#define ENDIANNESS ( *(const char *)&endianness == 0xef ? LITTLE \
                   : *(const char *)&endianness == 0xde ? BIG \
                   : assert(0))

а затем вы можете использовать ENDIANNESSмакрос по своему усмотрению.

Норман Рэмси
источник
6
Мне это нравится, потому что он признает существование порядка байтов, кроме маленького и большого.
Алок Сингхал,
6
Кстати, стоит вызвать макрос INT_ENDIANNESS или даже UINT32_T_ENDIANNESS, так как он проверяет только представление хранилища одного типа. Существует ARM ABI, где целочисленные типы являются прямым порядком байтов, а двойные - прямым порядком байтов (каждое слово является прямым порядком байтов, но слово со знаковым битом в нем стоит перед другим словом). Я могу вам сказать, что это вызвало некоторое волнение у команды компиляторов в течение дня или около того.
Стив Джессоп,
19

Если вы хотите полагаться только на препроцессор, вам нужно выяснить список предопределенных символов. Арифметика препроцессора не имеет понятия адресации.

GCC на Mac определяет __LITTLE_ENDIAN__или__BIG_ENDIAN__

$ gcc -E -dM - < /dev/null |grep ENDIAN
#define __LITTLE_ENDIAN__ 1

Затем вы можете добавить дополнительные условные директивы препроцессора на основе определения платформы, например, #ifdef _WIN32и т. Д.

Грегори Пакош
источник
6
GCC 4.1.2 в Linux, похоже, не определяет эти макросы, хотя GCC 4.0.1 и 4.2.1 определяют их в Macintosh. Так что это ненадежный метод для кросс-платформенной разработки, даже если вам разрешено указывать, какой компилятор использовать.
Роб Кеннеди,
1
о да, это потому, что он определяется только GCC на Mac.
Грегори Пакош 08
Примечание. Мой GCC (на Mac) определяет #define __BIG_ENDIAN__ 1и #define _BIG_ENDIAN 1.
clang 5.0.1 для OpenBSD / amd64 имеет #define __LITTLE_ENDIAN__ 1. Этот макрос кажется функцией clang, а не функцией gcc. На gccнекоторых компьютерах Mac команда не gcc, а clang.
Джордж Келер
GCC 4.2.1 на Mac тогда был GCC
Грегори Пакош
15

Я считаю, что это то, о чем просили. Я тестировал это только на машине с прямым порядком байтов под msvc. Кто-нибудь, пожалуйста, подтвердите на машине с прямым порядком байтов.

    #define LITTLE_ENDIAN 0x41424344UL 
    #define BIG_ENDIAN    0x44434241UL
    #define PDP_ENDIAN    0x42414443UL
    #define ENDIAN_ORDER  ('ABCD') 

    #if ENDIAN_ORDER==LITTLE_ENDIAN
        #error "machine is little endian"
    #elif ENDIAN_ORDER==BIG_ENDIAN
        #error "machine is big endian"
    #elif ENDIAN_ORDER==PDP_ENDIAN
        #error "jeez, machine is PDP!"
    #else
        #error "What kind of hardware is this?!"
    #endif

В качестве побочного примечания (для конкретного компилятора) с агрессивным компилятором вы можете использовать оптимизацию с «устранением мертвого кода» для достижения того же эффекта, что и во время компиляции, #ifнапример:

    unsigned yourOwnEndianSpecific_htonl(unsigned n)
    {
        static unsigned long signature= 0x01020304UL; 
        if (1 == (unsigned char&)signature) // big endian
            return n;
        if (2 == (unsigned char&)signature) // the PDP style
        {
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        if (4 == (unsigned char&)signature) // little endian
        {
            n = (n << 16) | (n >> 16);
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        // only weird machines get here
        return n; // ?
    }

Выше , основано на том , что компилятор распознает значения постоянная во время компиляции, полностью удаляет код внутри if (false) { ... }и заменяет код как if (true) { foo(); }с foo();наихудшим сценарием: компилятор не делает оптимизацию, вы все равно получите правильный код , но немного медленнее.

ggpp23
источник
Мне нравится этот метод, но поправьте меня, если я ошибаюсь: он работает только тогда, когда вы компилируете на машине, для которой строите, верно?
leetNightshade
3
gcc также выдает ошибку из-за многосимвольных констант. Таким образом, не переносной.
Эдвард Фальк
2
какой компилятор позволяет вам писать 'ABCD'?
Райан Хейнинг
2
Многие компиляторы разрешают многобайтовые символьные константы в режимах упрощенного соответствия, но запускают верхнюю часть с clang -Wpedantic -Werror -Wall -ansi foo.cошибкой. (Clang и это определенно: -Wfour-char-constants -Werror)
@Edward Falk Наличие в коде многосимвольной константы не является ошибкой . Это поведение, определяемое реализацией C11 6.4.4.4. 10. gcc и другие могут / не могут предупреждать / ошибаться в зависимости от настроек, но это не ошибка C. Конечно, использование многосимвольных констант не является популярным.
chux
10

Если вы ищете тест на время компиляции и используете gcc, вы можете:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

См. Документацию gcc для получения дополнительной информации.

Жером Пуйлер
источник
3
Это определенно лучший ответ для тех, кто использует gcc
rtpax
2
__BYTE_ORDER__доступно, начиная с GCC 4.6
Benoit Blanchon
8

Фактически вы можете получить доступ к памяти временного объекта, используя составной литерал (C99):

#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1})

Какой GCC будет оценивать во время компиляции.

u0b34a0f6ae
источник
Мне это нравится. Есть ли переносимый способ во время компиляции узнать, что вы компилируете под C99?
Эдвард Фальк
1
Ой, а что, если это не GCC?
Эдвард Фальк,
1
@EdwardFalk Да. #if __STDC_VERSION__ >= 199901L.
Jens
7

«Сетевая библиотека C» предлагает функции для обработки порядка байтов. А именно htons (), htonl (), ntohs () и ntohl () ... где n - это «сеть» (т. Е. Обратный порядок байтов), а h - «хост» (т. Е. Порядок байтов машины, на которой выполняется код).

Эти очевидные «функции» (обычно) определяются как макросы [см. <Netinet / in.h>], поэтому их использование не требует дополнительных затрат времени выполнения.

Следующие макросы используют эти «функции» для оценки порядка байтов.

#include <arpa/inet.h>
#define  IS_BIG_ENDIAN     (1 == htons(1))
#define  IS_LITTLE_ENDIAN  (!IS_BIG_ENDIAN)

К тому же:

Единственный раз, когда мне нужно знать порядок байтов системы, - это когда я записываю переменную [в файл / другой], которая может быть прочитана другой системой с неизвестным порядком байтов (для кросс-платформенной совместимости ) ... В таких случаях вы можете предпочесть напрямую использовать функции endian:

#include <arpa/inet.h>

#define JPEG_MAGIC  (('J'<<24) | ('F'<<16) | ('I'<<8) | 'F')

// Result will be in 'host' byte-order
unsigned long  jpeg_magic = JPEG_MAGIC;

// Result will be in 'network' byte-order (IE. Big-Endian/Human-Readable)
unsigned long  jpeg_magic = htonl(JPEG_MAGIC);
Голубых фишек
источник
Это не совсем ответ на вопрос, который искал быстрый способ определения порядка байтов.
Oren
@Oren: Что касается вашей обоснованной критики, я добавил детали, которые более прямо относятся к исходному вопросу.
BlueChip
6

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

Вы можете преобразовать его в короткий макрос, используя статическую или глобальную переменную, например:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
user231967
источник
Я считаю, что это лучший вариант, поскольку он самый простой. однако он не проверяет смешанный порядок байтов
Хайри Угур Колтук
1
Почему s_endianessдля начала не установлено значение 1?
SquareRootOfTwentyThree
5

Хотя нет переносимого #define или чего-то, на что можно положиться, платформы предоставляют стандартные функции для преобразования в исходный код вашего «хоста» и обратно.

Как правило, вы выполняете хранение - на диск или в сеть - с использованием метода обратного порядка байтов в сети, который является BIG endian, и локальных вычислений с использованием порядка байтов хоста (что на x86 является LITTLE endian). Вы используете htons()и ntohs()и друзей для преобразования между ними.

Будет
источник
4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

источник
6
Это также генерирует исполняемый код, а не константу. Вы не могли сделать "#if IS_BIG_ENDIAN"
Эдвард Фальк,
Мне нравится это решение, поскольку, насколько я понимаю, оно не зависит от неопределенного поведения стандартов C / C ++. Это не время компиляции, но единственное стандартное решение для этого ждет c ++ 20 std :: endian
ceztko
4

Не забывайте, что порядок байтов - это еще не все - размер charможет быть не 8 бит (например, DSP), отрицание дополнения до двух не гарантируется (например, Cray), может потребоваться строгое выравнивание (например, SPARC, также ARM переходит в середину -endian без выравнивания ) и т. д. и т. д.

Возможно, было бы лучше использовать конкретную архитектуру ЦП .

Например:

#if defined(__i386__) || defined(_M_IX86) || defined(_M_IX64)
  #define USE_LITTLE_ENDIAN_IMPL
#endif

void my_func()
{
#ifdef USE_LITTLE_ENDIAN_IMPL
  // Intel x86-optimized, LE implementation
#else
  // slow but safe implementation
#endif
}

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

ржавый
источник
3

Попробуй это:

#include<stdio.h>        
int x=1;
#define TEST (*(char*)&(x)==1)?printf("little endian"):printf("Big endian")
int main()
{

   TEST;
}
Prasoon Saurav
источник
2

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

К примеру , в ARM Cortex-M3 внедренная порядок байт будет отражать в битовом статус AIRCR.ENDIANNESS и компилятор не может знать это значение во время компиляции.

Вывод компиляции для некоторых из предложенных здесь ответов:

https://godbolt.org/z/GJGNE2 для этого ответа,

https://godbolt.org/z/Yv-pyJ для этого ответа и так далее.

Для ее решения вам нужно будет использовать volatileквалификатор. Yogeesh H T«S ответ ближе всего один для сегодняшнего реального использования жизни, но так как Christophпредполагает более комплексное решение, небольшое исправление к его ответу будет сделать ответ полным, просто добавьте volatileк объявлению союза: static const volatile union.

Это обеспечит сохранение и чтение из памяти, что необходимо для определения порядка байтов.

user2162550
источник
2

Если вы сбросите препроцессор #defines

gcc -dM -E - < /dev/null
g++ -dM -E -x c++ - < /dev/null

Обычно вы можете найти то, что вам поможет. С логикой времени компиляции.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Однако разные компиляторы могут иметь разные определения.

Сэм П
источник
0

Мой ответ не такой, как спросили, но действительно ли просто определить, является ли ваша система прямым или прямым порядком байтов?

Код:

#include<stdio.h>

int main()
{
  int a = 1;
  char *b;

  b = (char *)&a;
  if (*b)
    printf("Little Endian\n");
  else
    printf("Big Endian\n");
}
roottraveller
источник
0

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

int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // aliasing through char is ok
    puts("This system is little-endian");
else
    puts("This system is big-endian");
СМ АМРАН
источник
-3

Макрос для поиска порядка байтов

#define ENDIANNES() ((1 && 1 == 0) ? printf("Big-Endian"):printf("Little-Endian"))

или

#include <stdio.h>

#define ENDIAN() { \
volatile unsigned long ul = 1;\
volatile unsigned char *p;\
p = (volatile unsigned char *)&ul;\
if (*p == 1)\
puts("Little endian.");\
else if (*(p+(sizeof(unsigned long)-1)) == 1)\
puts("Big endian.");\
else puts("Unknown endian.");\
}

int main(void) 
{
       ENDIAN();
       return 0;
}
Йогиш HT
источник
3
Первый макрос неверен и всегда возвращает "Big-Endian". На сдвиг битов не влияет порядок байтов - порядок байтов влияет только на чтение и сохранение в памяти.
GaspardP