Por que essa estrutura tem tamanho 3 em vez de 2?

91

Eu defini esta estrutura:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

Eles sizeof(col)me dão a saída de 3, mas não deveria ser 2? Se eu comentar apenas um elemento, o sizeofé 2. Não entendo por quê: cinco elementos de 3 bits são iguais a 15 bits, e isso é menor que 2 bytes.

Existe um "tamanho interno" na definição de uma estrutura como esta? Só preciso de um esclarecimento, porque pela minha noção da linguagem até agora, esperava um tamanho de 2 bytes, não 3.

Raffaello
fonte
4
Provavelmente é a otimização do alinhamento. Ele inicia um novo byte, se o próximo tamanho de bit não couber no espaço ocupado real.
πάντα ῥεῖ
4
A menos que você tenha algumas restrições externas que requeiram a compactação de bits e sua plataforma forneça algumas garantias adicionais sobre o que o padrão oferece, não faz sentido usar bitfields.
David Rodríguez - dribeas 02/11/2014
3
Observe que, para C, usar char é menos portátil do que usar int, stackoverflow.com/a/23987436/23118 .
hlovdal
2
Observe que quase tudo sobre campos de bits é definido pela implementação. Você pode obter respostas diferentes de compiladores diferentes, e não há recurso. Observe também que porque você não especificou signed charou unsigned char, você não pode dizer sem olhar a documentação se o compilador tratará 'simples' charem um campo de bits como assinado ou não, e a decisão poderia (em teoria) ser diferente da decisão sobre se 'plain' charé assinado ou não quando usado fora de um campo de bits.
Jonathan Leffler
3
Especificamente, na C99, §6.7.2.1 Struct e especificadores sindicais, ¶4 Um pouco-campo deve ter um tipo que é uma versão qualificado ou não qualificado _Bool, signed int, unsigned int, ou algum outro tipo definido pela implementação. charPortanto, usar se enquadra na categoria de 'outro tipo definido pela implementação'.
Jonathan Leffler

Respostas:

95

Como você está usando charcomo tipo subjacente para seus campos, o compilador tenta agrupar bits por bytes e, como não pode colocar mais de oito bits em cada byte, ele pode armazenar apenas dois campos por byte.

A soma total de bits que sua estrutura usa é 15, então o tamanho ideal para caber tantos dados seria a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

O código acima (para uma plataforma de 64 bits como a minha) realmente renderá 2para a segunda estrutura. Para qualquer coisa maior que a short, a estrutura não preencherá mais de um elemento do tipo usado, portanto - para essa mesma plataforma - a estrutura terminará com o tamanho quatro para int, oito para long, etc.

didierc
fonte
1
A definição da estrutura proposta ainda está errada. A definição de struct correta usaria 'short sem sinal'.
user3629249
21
@ user3629249 Por que o short sem sinal é 'correto'? Se o usuário quiser armazenar de -4 a 3, o curto está correto. Se o usuário quiser armazenar de 0 a 7, o curto sem sinal está correto. A pergunta original usava um tipo assinado, mas não posso dizer se foi intencional ou acidental.
Bruce Dawson
2
Por que há diferença entre chare short?
GingerPlusPlus
5
@BruceDawson: O padrão permite que as implementações não charsejam assinadas ...
Thomas Eding
@ThomasEding True, o padrão permite que char não tenha sinal. Mas meu ponto principal permanece, que nenhuma razão foi dada para afirmar que a curta metragem sem sinal estava correta (embora geralmente seja).
Bruce Dawson
78

Porque você não pode ter um campo de pacote de bits que ultrapasse o limite mínimo de alinhamento (que é 1 byte), então eles provavelmente serão empacotados como

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(as ordens de campo / preenchimento dentro do mesmo byte não são intencionais, é apenas para dar uma ideia, já que o compilador poderia defini-las como preferir)

Jack
fonte
16

Os primeiros dois campos de bits se encaixam em um único char. O terceiro não se encaixa nisso chare precisa de um novo. 3 + 3 + 3 = 9 que não cabe em um caractere de 8 bits.

Assim, o primeiro par recebe a char, o segundo par recebe a chare o campo do último bit obtém um terceiro char.

2501
fonte
15

A maioria dos compiladores permite que você controle o preenchimento, por exemplo#pragma , usando s . Aqui está um exemplo com GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Observe que o comportamento padrão do compilador existe por uma razão e provavelmente proporcionará melhor desempenho.

Kos
fonte
9

Embora o padrão ANSI C especifique muito pouco sobre como os campos de bits são compactados para oferecer qualquer vantagem significativa sobre "os compiladores têm permissão para compactar os campos de bits da maneira que acharem conveniente", ainda assim, em muitos casos, proíbe os compiladores de compactar as coisas da maneira mais eficiente.

Em particular, se uma estrutura contém campos de bits, um compilador é necessário para armazená-la como uma estrutura que contém um ou mais campos anônimos de algum tipo de armazenamento "normal" e então subdividir logicamente cada campo em suas partes de campo de bits constituintes. Assim, dado:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Se unsigned charfosse de 8 bits, o compilador seria obrigado a alocar quatro campos desse tipo e atribuir dois campos de bits a todos, exceto um (que estaria em um charcampo próprio). Se todas as chardeclarações tivessem sido substituídas por short, haveria dois campos do tipo short, um dos quais conteria cinco campos de bits e o outro conteria os dois restantes.

Em um processador sem restrições de alinhamento, os dados poderiam ser dispostos de forma mais eficiente usando unsigned shortpara os primeiros cinco campos e unsigned charpara os dois últimos, armazenando sete campos de três bits em três bytes. Embora deva ser possível armazenar oito campos de três bits em três bytes, um compilador só poderia permitir isso se existisse um tipo numérico de três bytes que pudesse ser usado como o tipo de "campo externo".

Pessoalmente, considero os campos de bits definidos como basicamente inúteis. Se o código precisa trabalhar com dados compactados em binários, ele deve definir explicitamente os locais de armazenamento dos tipos reais e, em seguida, usar macros ou algum outro meio para acessar os bits deles. Seria útil se C suportasse uma sintaxe como:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Essa sintaxe, se permitida, tornaria possível para o código usar campos de bits de uma forma portátil, sem levar em conta os tamanhos das palavras ou ordenações de bytes (foo0 estaria nos três bits menos significativos de f1, mas aqueles poderiam ser armazenados no endereço inferior ou superior). Na ausência desse recurso, no entanto, as macros são provavelmente a única maneira portátil de operar com essas coisas.

supergato
fonte
2
Compiladores diferentes apresentam campos de bits de maneira diferente. Eu escrevi alguma documentação sobre como o Visual C ++ faz isso que pode ser relevante. Ele aponta algumas das armadilhas irritantes: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Bem, você está dizendo um equivalente a armazenar em um tipo normal e usar o operador de campo de bits para realizar a única variável de interesse e para simplificar esse mecanismo, use alguma macro. Acho que o código gerado em c / c ++ está fazendo algo assim também. Usar uma estrutura é apenas para uma organização "melhor" do código; na verdade, não é necessário.
Raffaello