C / C ++: Forçar ordem e alinhamento de campo de bits

87

Eu li que a ordem dos campos de bits em uma estrutura é específica da plataforma. E se eu usar diferentes opções de empacotamento específicas do compilador, esses dados de garantia serão armazenados na ordem correta conforme são escritos? Por exemplo:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

Em um processador Intel com o compilador GCC, os campos foram dispostos na memória conforme são mostrados. Message.versionforam os primeiros 3 bits no buffer e os Message.typeseguiram. Se eu encontrar opções de empacotamento de struct equivalentes para vários compiladores, isso será multiplataforma?

Dewald
fonte
17
Uma vez que um buffer é um conjunto de bytes, não bits, "os primeiros 3 bits no buffer" não é um conceito preciso. Você consideraria os 3 bits de ordem inferior do primeiro byte como os 3 primeiros bits ou os 3 bits de ordem superior?
caf
2
Ao transitar na rede, "Os primeiros 3 bits no buffer" acabam sendo muito bem definidos.
Joshua
2
@Joshua IIRC, a Ethernet transmite o bit menos significativo de cada byte primeiro (é por isso que o bit de transmissão está onde está).
tc.
Quando você diz "portátil" e "plataforma cruzada", o que você quer dizer? O executável acessará corretamente o pedido, independentemente do SO alvo - ou - o código será compilado independentemente do conjunto de ferramentas?
Garet Claborn

Respostas:

103

Não, não será totalmente portátil. As opções de embalagem para estruturas são extensões e não são totalmente portáteis. Além disso, C99 §6.7.2.1, parágrafo 10 diz: "A ordem de alocação de campos de bits dentro de uma unidade (ordem alta para ordem inferior ou ordem inferior para ordem alta) é definida pela implementação."

Mesmo um único compilador pode definir o campo de bits de forma diferente, dependendo do endianness da plataforma de destino, por exemplo.

Stephen Canon
fonte
Sim, o GCC, por exemplo, observa especificamente que os bitfields são organizados de acordo com a ABI, não a implementação. Portanto, apenas ficar em um único compilador não é suficiente para garantir a ordem. A arquitetura também deve ser verificada. Um pesadelo para portabilidade, na verdade.
sublinhado_d
10
Por que o padrão C não garantiu um pedido de campos de bits?
Aaron Campbell
7
É difícil definir a "ordem" de bits dentro dos bytes de forma consistente e portável, muito menos a ordem dos bits que podem cruzar os limites dos bytes. Qualquer definição que você escolher falhará em corresponder a uma quantidade considerável de prática existente.
Stephen Canon
2
definido pela implementação permite a otimização específica da plataforma. Em algumas plataformas, o preenchimento entre os campos de bits pode melhorar o acesso, imagine quatro campos de sete bits em um interno de 32 bits: alinhá-los a cada 8 bits é uma melhoria significativa para plataformas que têm leituras de bytes.
peterchen,
não packedimpor ordenando: stackoverflow.com/questions/1756811/... como impor ordenação bit: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Ciro Santilli郝海东冠状病六四事件法轮功
45

Os campos de bits variam amplamente de compilador para compilador, desculpe.

Com o GCC, as máquinas big endian exibem as partes big end primeiro e as máquinas little endian exibem as partes little endian primeiro.

K&R diz "Membros de campo adjacentes [bit-] de estruturas são empacotados em unidades de armazenamento dependentes de implementação em uma direção dependente de implementação. Quando um campo seguindo outro campo não caberá ... ele pode ser dividido entre unidades ou a unidade pode ser preenchido. Um campo sem nome de largura 0 força este preenchimento ... "

Portanto, se você precisa de um layout binário independente da máquina, você deve fazer isso sozinho.

Esta última declaração também se aplica a campos não-bit devido ao preenchimento - entretanto, todos os compiladores parecem ter alguma maneira de forçar o empacotamento de bytes de uma estrutura, como vejo que você já descobriu para o GCC.

Joshua
fonte
K&R é realmente considerado uma referência útil, dado que foi pré-padronização e (presumo?) Provavelmente foi substituído em muitas áreas?
sublinhado_d
1
Meu K&R é pós-ANSI.
Josué
1
Agora, isso é constrangedor: eu não sabia que eles lançaram uma revisão pós-ANSI. Foi mal!
sublinhado_d
35

Bitfields devem ser evitados - eles não são muito portáveis ​​entre compiladores, mesmo para a mesma plataforma. da norma C99 6.7.2.1/10 - "Especificadores de estrutura e união" (há formulação semelhante na norma C90):

Uma implementação pode alocar qualquer unidade de armazenamento endereçável grande o suficiente para conter um campo de bits. Se houver espaço suficiente, um campo de bits que segue imediatamente outro campo de bits em uma estrutura deve ser compactado em bits adjacentes da mesma unidade. Se espaço insuficiente permanecer, se um campo de bits que não se ajusta é colocado na próxima unidade ou se sobrepõe a unidades adjacentes, é definido pela implementação. A ordem de alocação dos campos de bits dentro de uma unidade (ordem alta para ordem baixa ou ordem baixa para ordem alta) é definida pela implementação. O alinhamento da unidade de armazenamento endereçável não é especificado.

Você não pode garantir se um campo de bits irá 'ultrapassar' um limite interno ou não e você não pode especificar se um campo de bits começa na extremidade inferior do int ou no extremo superior do int (isto é independente se o processador é big-endian ou little-endian).

Prefira bitmasks. Use inlines (ou mesmo macros) para definir, limpar e testar os bits.

Michael Burr
fonte
2
A ordem dos campos de bits pode ser determinada em tempo de compilação.
Greg A. Woods
9
Além disso, os campos de bits são altamente preferidos ao lidar com sinalizadores de bits que não têm representação externa fora do programa (ou seja, no disco ou em registros ou na memória acessada por outros programas, etc).
Greg A. Woods
1
@ GregA.Woods: Se esse for o caso, forneça uma resposta descrevendo como. Não consegui encontrar nada além do seu comentário ao pesquisar por ele no Google ...
mozzbozz
1
@ GregA.Woods: Desculpe, deveria ter escrito a qual comentário me referi. Eu quis dizer: você diz que "A ordem dos campos de bits pode ser determinada em tempo de compilação." Não sei nada sobre isso e como fazer.
mozzbozz
2
@mozzbozz Dê uma olhada em planix.com/~woods/projects/wsg2000.c e pesquise por definições e uso de _BIT_FIELDS_LTOHe_BIT_FIELDS_HTOL
Greg A. Woods
11

endianness está falando sobre ordens de bytes, não ordens de bits. Hoje em dia , é 99% certo que os pedidos de bits são fixos. No entanto, ao usar bitfields, endianness deve ser levado em conta. Veja o exemplo abaixo.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
Pierrotlefou
fonte
6
A saída de aeb indica que endianness ainda está falando sobre ordens de bits E ordens de bytes.
Programador Windows
exemplo maravilhoso com ordenação de bits e problemas de ordenação de bytes
Jonathan
1
Você realmente compilou e executou o código? Os valores para "a" e "b" não parecem lógicos para mim: você está basicamente dizendo que o compilador irá trocar os nibbles dentro de um byte por causa do endianness. No caso de "d", endiannes não deve afetar a ordem de bytes dentro de matrizes char (assumindo que char tem 1 byte); se o compilador fizesse isso, não seríamos capazes de iterar por meio de um array usando ponteiros. Se, por outro lado, você usou um array de dois inteiros de 16 bits, por exemplo: uint16 data [] = {0x1234,0x5678}; então d seria definitivamente 0x7856 em sistemas little endian.
Krauss de
6

Na maioria das vezes, provavelmente, mas não aposte muito nisso, porque se você estiver errado, perderá muito.

Se você realmente precisa ter informações binárias idênticas, você precisará criar bitfields com bitmasks - por exemplo, você usa um curto sem sinal (16 bits) para Message e depois faz coisas como versionMask = 0xE000 para representar os três bits superiores.

Existe um problema semelhante com o alinhamento dentro das estruturas. Por exemplo, Sparc, PowerPC e CPUs 680x0 são big-endian, e o padrão comum para compiladores Sparc e PowerPC é alinhar membros de estrutura em limites de 4 bytes. No entanto, um compilador que usei para 680x0 apenas alinhou em limites de 2 bytes - e não havia opção para alterar o alinhamento!

Portanto, para algumas estruturas, os tamanhos em Sparc e PowerPC são idênticos, mas menores em 680x0, e alguns dos membros estão em deslocamentos de memória diferentes dentro da estrutura.

Este era um problema com um projeto em que trabalhei, porque um processo de servidor em execução no Sparc consultaria um cliente e descobriria que era big-endian, e assumiria que poderia apenas espalhar structs binários na rede e o cliente poderia lidar com isso. E funcionou bem em clientes PowerPC e travou bastante em clientes 680x0. Não escrevi o código e demorou um pouco para encontrar o problema. Mas foi fácil consertar depois que fiz.

Bob Murphy
fonte
1

Obrigado @BenVoigt pelo seu comentário muito útil começando

Não, eles foram criados para economizar memória.

Source Linux faz usar um campo de bits para corresponder a uma estrutura externa: /usr/include/linux/ip.h tem esse código para o primeiro byte de um datagrama IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

No entanto, à luz do seu comentário, estou desistindo de tentar fazer isso funcionar para o campo de bits multibyte frag_off .

Duncan Roe
fonte
-9

Claro que a melhor resposta é usar uma classe que lê / escreve campos de bits como um fluxo. Usar a estrutura de campo de bits C não é garantido. Sem mencionar que é considerado pouco profissional / preguiçoso / estúpido usar isso na codificação do mundo real.

99999999
fonte
5
Acho que é errado afirmar que é estúpido usar campos de bits, pois fornece uma maneira muito limpa de representar os registros de hardware, os quais foram criados para modelar, em C.
trondd
13
@trondd: Não, eles foram criados para economizar memória. Os bitfields não se destinam a mapear para estruturas de dados externas, como registros de hardware mapeados em memória, protocolos de rede ou formatos de arquivo. Se eles tivessem a intenção de mapear para estruturas de dados externas, o pedido de embalagem teria sido padronizado.
Ben Voigt
2
O uso de bits economiza memória. O uso de campos de bits aumenta a legibilidade. Usar menos memória é mais rápido. O uso de bits permite operações atômicas mais complexas. Em nossos aplicativos do mundo real, há necessidade de desempenho e operações atômicas complexas. Essa resposta não funcionaria para nós.
johnnycrash
@BenVoigt provavelmente é verdade, mas se um programador estiver disposto a confirmar que a ordem de seu compilador / ABI corresponde ao que eles precisam e sacrificar a portabilidade rápida de acordo - então certamente eles podem cumprir essa função. Quanto ao 9 *, que massa autorizada de "codificadores do mundo real" considera todo o uso de bitfields "não profissional / preguiçoso / estúpido" e onde eles afirmam isso?
sublinhado_d
2
Usar menos memória nem sempre é mais rápido; geralmente é mais eficiente usar mais memória e reduzir as operações de pós-leitura, e o modo processador / processador pode tornar isso ainda mais verdadeiro.
Dave Newton