C Definição de macro para determinar a máquina big endian ou little endian?

107

Existe uma definição de macro de uma linha para determinar o endianness da máquina. Estou usando o código a seguir, mas convertê-lo em macro seria muito longo.

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

    return (test_endian[0] == 0);
}
manav mn
fonte
2
Por que não incluir o mesmo código em uma macro?
sharptooth de
4
Você não pode determinar o endianness portável com o pré-processador C sozinho. Você também quer 0, em vez de NULLem seu teste final, e alterar um dos test_endianobjetos para outra coisa :-).
Alok Singhal de
2
Além disso, por que uma macro é necessária? A função inline faria o mesmo e é muito mais segura.
sharptooth de
13
@Sharptooth, uma macro é atraente porque seu valor pode ser conhecido no momento da compilação, o que significa que você pode usar o endianness da sua plataforma para controlar a instanciação do template, por exemplo, ou talvez até selecionar diferentes blocos de código com uma #ifdiretiva.
Rob Kennedy,
3
Isso é verdade, mas ineficiente. Se eu tiver uma cpu little-endian e estiver gravando dados little-endian na conexão ou em um arquivo, prefiro evitar descompactar e reembalar dados sem nenhum propósito. Eu costumava escrever drivers de vídeo para viver. É extremamente importante, ao gravar pixels em uma placa de vídeo, otimizar todos os lugares possíveis.
Edward Falk

Respostas:

102

Código que suporta ordens de bytes arbitrárias, pronto para ser colocado em um arquivo chamado 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

Você verificaria os sistemas little endian via

O32_HOST_ORDER == O32_LITTLE_ENDIAN
Christoph
fonte
11
Isso não permite que você decida o endian-ness até o tempo de execução. O seguinte falha ao compilar porque. / ** isLittleEndian :: result -> 0 ou 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; };
user48956
3
É impossível obter resultados até o tempo de execução?
k06a
8
Porque char? Melhor usar uint8_te falhar se este tipo não estiver disponível (o que pode ser verificado por #if UINT8_MAX). Observe que CHAR_BITé independente de uint8_t.
Andreas Spindler
2
Este é UB em c ++: stackoverflow.com/questions/11373203/…
Lyberta 01 de
3
Deixe-me acrescentar mais um na mistura, para completar:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk
49

Se você tiver um compilador compatível com literais compostos C99:

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

ou:

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

Em geral, porém, você deve tentar escrever um código que não dependa do endianness da plataforma host.


Exemplo de implementação independente de host-endianness de 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];
}
caf
fonte
3
"você deve tentar escrever um código que não dependa do endianness da plataforma host". Infelizmente, meu apelo, "Eu sei que estamos escrevendo uma camada de compatibilidade POSIX, mas não quero implementar ntoh, porque depende do endianness da plataforma host" sempre caiu em ouvidos moucos ;-). Manipulação de formato gráfico e código de conversão é o outro candidato principal que eu vi - você não quer basear tudo em chamar ntohl o tempo todo.
Steve Jessop de
5
Você pode implementar ntohlde uma maneira que não dependa do endianness da plataforma host.
café de
1
@caf, como você escreveria ntohl de uma maneira independente de host-endianness?
Hayri Uğur Koltuk
3
@AliVeli: Eu adicionei um exemplo de implementação à resposta.
café de
6
Devo também acrescentar, para registro, que "(* (uint16_t *)" \ 0 \ xff "<0x100)" não compilará em uma constante, não importa o quanto eu otimize, pelo menos com gcc 4.5.2. Ele sempre cria código executável.
Edward Falk
43

Não existe um padrão, mas em muitos sistemas, incluindo <endian.h>lhe dará algumas definições para procurar.

Ignacio Vazquez-Abrams
fonte
30
Teste o endianness com #if __BYTE_ORDER == __LITTLE_ENDIANe #elif __BYTE_ORDER == __BIG_ENDIAN. E gerar um outro #errorsentido.
To1ne
6
<endian.h>não está disponível no Windows
rustyx
2
Projetos Android e Chromium usam, a endian.hmenos que __APPLE__ou _WIN32seja definido.
patryk.beza
1
No OpenBSD 6.3, <endian.h> fornece #if BYTE_ORDER == LITTLE_ENDIAN(ou BIG_ENDIAN) sem sublinhados antes dos nomes. _BYTE_ORDERé apenas para cabeçalhos do sistema. __BYTE_ORDERnão existe.
George Koehler
@ To1ne Duvido que Endianness seja relevante para o Windows, já que o Windows (pelo menos atualmente) roda apenas em máquinas x86 e ARM. x86 sempre sendo LE e ARM sendo configurável para usar qualquer uma das arquiteturas.
SimonC
27

Para detectar endianness em tempo de execução, você deve ser capaz de se referir à memória. Se você seguir o padrão C, declarar uma variável na memória requer uma declaração, mas retornar um valor requer uma expressão. Não sei como fazer isso em uma única macro - é por isso que o gcc tem extensões :-)

Se você deseja ter um arquivo .h, pode definir

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

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

e então você pode usar a ENDIANNESSmacro como quiser.

Norman Ramsey
fonte
6
Gosto disso porque reconhece a existência de outros endianismos além do pequeno e do grande.
Alok Singhal de
6
Falando nisso, pode valer a pena chamar a macro INT_ENDIANNESS, ou mesmo UINT32_T_ENDIANNESS, já que ela testa apenas a representação de armazenamento de um tipo. Há um ARM ABI em que os tipos integrais são little-endian, mas duplos são middle-endian (cada palavra é little-endian, mas a palavra com o bit de sinal vem antes da outra palavra). Isso causou certo entusiasmo na equipe de compiladores por um ou dois dias, posso dizer.
Steve Jessop de
19

Se você quiser confiar apenas no pré-processador, terá que descobrir a lista de símbolos predefinidos. A aritmética do pré-processador não tem conceito de endereçamento.

GCC no Mac define __LITTLE_ENDIAN__ou__BIG_ENDIAN__

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

Então, você pode adicionar mais diretivas condicionais de pré-processador com base na detecção de plataforma, como #ifdef _WIN32etc.

Gregory Pakosz
fonte
6
O GCC 4.1.2 no Linux não parece definir essas macros, embora o GCC 4.0.1 e 4.2.1 as defina no Macintosh. Portanto, não é um método confiável para desenvolvimento de plataforma cruzada, mesmo quando você tem permissão para ditar qual compilador usar.
Rob Kennedy,
1
sim, é porque só é definido pelo GCC no Mac.
Gregory Pakosz
Nota: Meu GCC (no Mac) define #define __BIG_ENDIAN__ 1e #define _BIG_ENDIAN 1.
clang 5.0.1 para OpenBSD / amd64 tem #define __LITTLE_ENDIAN__ 1. Esta macro parece ser um recurso clang, não um recurso gcc. O gcccomando em alguns Macs não é gcc, é clang.
George Koehler
O GCC 4.2.1 no Mac era o GCC naquela época
Gregory Pakosz
15

Eu acredito que é isso que foi pedido. Eu só testei isso em uma pequena máquina endian no msvc. Alguém, por favor, confirme em uma máquina big endian.

    #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

Como uma observação lateral (específica do compilador), com um compilador agressivo, você pode usar a otimização de "eliminação de código morto" para obter o mesmo efeito que um tempo de compilação #ifcomo:

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

O acima se baseia no fato de que o compilador reconhece os valores constantes em tempo de compilação, remove inteiramente o código dentro if (false) { ... }e substitui o código como if (true) { foo(); }com foo();O pior cenário: o compilador não faz a otimização, você ainda obtém o código correto, mas um pouco mais lento.

ggpp23
fonte
Eu gosto desse método, mas me corrija se eu estiver errado: isso só funciona quando você está compilando na máquina para a qual está construindo, correto?
leetNightshade
3
O gcc também gera um erro devido a constantes de vários caracteres. Portanto, não é portátil.
Edward Falk
2
qual compilador está deixando você escrever 'ABCD'?
Ryan Haining
2
Muitos compiladores permitirão constantes de caracteres multibyte em modos de conformidade relaxados, mas execute a parte superior com clang -Wpedantic -Werror -Wall -ansi foo.ce ocorrerá um erro. (Clang e isto especificamente: -Wfour-char-constants -Werror)
@Edward Falk Não é um erro ter uma constante de vários caracteres no código. É o comportamento definido pela implementação C11 6.4.4.4. 10. gcc e outros podem / não podem avisar / errar dependendo das configurações, mas não é um erro C. Certamente não é comum usar constantes de caracteres com vários caracteres.
chux - Reintegrar Monica
10

Se você está procurando um teste de tempo de compilação e está usando o gcc, pode fazer:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Veja a documentação do gcc para mais informações.

Jérôme Pouiller
fonte
3
Esta é definitivamente a melhor resposta para quem usa gcc
rtpax
2
__BYTE_ORDER__está disponível desde GCC 4.6
Benoit Blanchon
8

Você pode , de fato, acessar a memória de um objeto temporário usando um literal composto (C99):

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

Qual GCC avaliará em tempo de compilação.

u0b34a0f6ae
fonte
Eu gosto disso. Existe uma maneira portátil e em tempo de compilação de saber que você está compilando em C99?
Edward Falk
1
Ah, e se não for o GCC?
Edward Falk
1
@EdwardFalk Sim. #if __STDC_VERSION__ >= 199901L.
Jens
7

A 'biblioteca de rede C' oferece funções para lidar com endian'ness. Nomeadamente htons (), htonl (), ntohs () e ntohl () ... onde n é "rede" (ou seja, big-endian) eh é "host" (ou seja, o endian'ness da máquina que executa o código).

Essas aparentes 'funções' são (comumente) definidas como macros [consulte <netinet / in.h>], portanto, não há sobrecarga de tempo de execução para usá-las.

As macros a seguir usam essas 'funções' para avaliar o endianismo.

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

Além do que, além do mais:

A única vez que preciso saber o endian'ness de um sistema é quando escrevo uma variável [para um arquivo / outro] que pode ser lida por outro sistema de endian'ness desconhecido (para compatibilidade entre plataformas ) ... Em casos como esses, você pode preferir usar as funções endian diretamente:

#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);
Chip azul
fonte
Isso realmente não responde à pergunta que procurava uma maneira rápida de determinar o endianismo.
Oren
@Oren: Com relação à sua crítica válida, incluí detalhes que abordam a questão original mais diretamente.
BlueChip
6

Use uma função embutida em vez de uma macro. Além disso, você precisa armazenar algo na memória, o que é um efeito colateral não muito bom de uma macro.

Você pode convertê-lo em uma macro curta usando uma variável estática ou global, como esta:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)
user231967
fonte
eu acho que isso é o melhor, pois é o mais simples. no entanto, não testa contra endian misto
Hayri Uğur Koltuk
1
Por que não está s_endianessdefinido como 1 para começar?
SquareRootOfTwentyThree
5

Embora não haja um #define portátil ou algo em que se possa confiar, as plataformas fornecem funções padrão para a conversão de e para o seu 'host' endian.

Geralmente, você faz armazenamento - em disco ou rede - usando 'endian de rede', que é BIG endian, e computação local usando host endian (que em x86 é LITTLE endian). Você usa htons()e ntohs()e amigos para converter entre os dois.

Vai
fonte
4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

fonte
6
Isso também gera código executável, não uma constante. Você não poderia fazer "#if IS_BIG_ENDIAN"
Edward Falk
Eu gosto dessa solução porque ela não depende de comportamento indefinido dos padrões C / C ++, tanto quanto eu entendo. Não é tempo de compilação, mas a única solução padrão para isso é esperar por c ++ 20 std :: endian
ceztko
4

Não se esqueça de que endianness não é toda a história - o tamanho de char pode não ser de 8 bits (por exemplo, DSP's), a negação do complemento de dois não é garantida (por exemplo, Cray), o alinhamento estrito pode ser necessário (por exemplo, SPARC, também ARM salta para o meio -endian quando desalinhado), etc, etc.

Pode ser uma ideia melhor direcionar um específico arquitetura de CPU .

Por exemplo:

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

Observe que esta solução também não é ultra-portátil, infelizmente, pois depende de definições específicas do compilador (não existe um padrão, mas aqui está uma boa compilação de tais definições).

enferrujado
fonte
3

Experimente isto:

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

   TEST;
}
Prasoon Saurav
fonte
2

Por favor, preste atenção que a maioria das respostas aqui não são portáveis, uma vez que os compiladores atuais irão avaliar essas respostas em tempo de compilação (depende da otimização) e retornar um valor específico baseado em um endianness específico, enquanto o endianness real da máquina pode ser diferente. Os valores nos quais o endianness é testado, nunca chegarão à memória do sistema, portanto, o código real executado retornará o mesmo resultado, independentemente do endianness real.

Por exemplo , em ARM Cortex-M3, o endianness implementado refletirá em um bit de status AIRCR.ENDIANNESS e o compilador não pode saber esse valor em tempo de compilação.

Resultado da compilação para algumas das respostas sugeridas aqui:

https://godbolt.org/z/GJGNE2 para esta resposta,

https://godbolt.org/z/Yv-pyJ para isso resposta e assim por diante.

Para resolvê-lo, você precisará usar o volatilequalificador. Yogeesh H T's resposta é a mais próxima para o uso da vida real de hoje, mas desde que Christophsugere solução mais abrangente, uma ligeira correção para a sua resposta faria a resposta completa, basta adicionar volatileà declaração da União: static const volatile union.

Isso garantiria o armazenamento e a leitura da memória, necessários para determinar o endianismo.

user2162550
fonte
2

Se você despejar o pré-processador #defines

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

Normalmente, você pode encontrar coisas que irão ajudá-lo. Com lógica de tempo de compilação.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Vários compiladores podem ter diferentes definições, no entanto.

Sam P
fonte
0

Minha resposta não é a perguntada, mas é realmente simples descobrir se seu sistema é little endian ou big endian?

Código:

#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
fonte
0

Código C para verificar se um sistema é little-endian ou big-indian.

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");
SM AMRAN
fonte
-3

Macro para encontrar endiannes

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

ou

#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;
}
Yogeesh HT
fonte
3
A primeira macro está incorreta e sempre retornará "Big-Endian". O deslocamento de bits não é afetado pelo endianness - o endianness afeta apenas leituras e armazenamentos na memória.
GaspardP