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
Чому б не включити той самий код у макрос?
гострий зуб

4
Неможливо портативно визначити витривалість лише за допомогою препроцесора C. Ви також хочете 0замість NULLсвого останнього тесту і змінити один із test_endianоб'єктів на щось інше :-).
Алок Сінгал

2
Також для чого потрібен макрос? Функція Inline зробила б те саме і набагато безпечніше.
гострий зуб

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 :: результат -> 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.
Андреас Шпіндлер


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

2
Android і хромові проекти не використовувати , endian.hякщо __APPLE__або _WIN32визначений.
patryk.beza

1
У OpenBSD 6.3 <endian.h> надає #if BYTE_ORDER == LITTLE_ENDIAN(або BIG_ENDIAN) відсутність підкреслень перед іменами. _BYTE_ORDERпризначено лише для заголовків системи. __BYTE_ORDERне існує.
Джордж Келер

@ To1ne Я сумніваюся, що Endianness є актуальною для 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.
Григорій Пакош

Примітка: Мій GCC (на Mac) визначає #define __BIG_ENDIAN__ 1і #define _BIG_ENDIAN 1.

clang 5.0.1 для OpenBSD / amd64 має #define __LITTLE_ENDIAN__ 1. Здається, цей макрос є кланг-функцією, а не функцією gcc. gccКоманда в деяких комп'ютерах Mac НЕ НКА, це брязкіт.
Джордж Келер

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();Найгірший сценарій: компілятор не робить оптимізацію, ви все одно отримуєте правильний код, але трохи повільніше.


Мені подобається цей метод, але виправте мене, якщо я помиляюся: це працює лише тоді, коли ви збираєте машину, для якої ви будуєте, правда?
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
Бенуа Бланшон

8

Фактично ви можете отримати доступ до пам'яті тимчасового об'єкта, використовуючи складений літерал (C99):

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

Який GCC оцінить під час компіляції.


Мені це подобається. Чи існує портативний спосіб компіляції, який знає, що ви компілюєте під C99?
Едвард Фолк

1
О, а що, якщо це не GCC?
Едвард Фолк

1
@EdwardFalk Так. #if __STDC_VERSION__ >= 199901L.
Єнс

7

"Мережева бібліотека С" пропонує функції для обробки витримки. А саме htons (), htonl (), ntohs () і ntohl () ... де n - "мережа" (тобто. Big-endian), а h - "господар" (тобто ендіантність машини, що працює на код).

Ці видимі "функції" (зазвичай) визначаються як макроси [див. <Netinet / in.h>], тому для їх використання немає накладних витрат на виконання.

Наступні макроси використовують ці "функції" для оцінки витримки.

#include <arpa/inet.h>
#define  IS_BIG_ENDIAN     (1 == htons(1))
#define  IS_LITTLE_ENDIAN  (!IS_BIG_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: Що стосується вашої справедливої ​​критики, я заздалегідь створив детальну інформацію, яка стосується оригінального питання безпосередньо.
BlueChip

6

Використовуйте вбудовану функцію, а не макрос. Крім того, вам потрібно зберегти щось у пам'яті, що є не дуже приємним побічним ефектом макросу.

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

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)

Я думаю, що це найкраще, оскільки це найпростіше. однак це не тестує на змішаний ендіан
Хайрі Угур Колтук

1
Чому s_endianessдля початку не встановлено значення 1?
SquareRootOfWentyThree

5

Поки немає портативного #define або чогось, на що можна покластися, платформи надають стандартні функції для перетворення на ендіан вашого "хоста" та з нього.

Як правило, ви робите сховище - на диск або мережу - використовуючи "мережевий ендіан", який є BIG endian, і локальний обчислення, використовуючи хост ендіан (який на x86 - ЛІТТЕ ендіан). Ви використовуєте 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;
}

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.

Це гарантувало б збереження та читання з пам'яті, яке потрібно для визначення витривалості.


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

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

3
Перший макрос є неправильним і завжди повертатиме "Big-Endian". На зсув бітів не впливає ендіанс - ендіанс впливає лише на читання і зберігання в пам'яті.
GaspardP
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.