Por que não é sizeof para uma struct igual à sum do tamanho de cada membro?

Por que o operador ‘sizeof‘ retorna um tamanho maior para uma estrutura do que o tamanho total dos membros da estrutura?

Isso ocorre porque o preenchimento foi adicionado para satisfazer as restrições de alinhamento. O alinhamento da estrutura de dados afeta tanto o desempenho quanto a correção dos programas:

  • O access desalinhado pode ser um erro difícil (geralmente SIGBUS ).
  • O access desalinhado pode ser um erro temporário.
    • Ou corrigido em hardware, para uma modesta degradação de desempenho.
    • Ou corrigido pela emulação no software, para uma grave degradação de desempenho.
    • Além disso, a atomicidade e outras garantias de concorrência podem ser quebradas, levando a erros sutis.

Aqui está um exemplo usando configurações típicas para um processador x86 (todos os modos usados ​​de 32 e 64 bits):

 struct X { short s; /* 2 bytes */ /* 2 padding bytes */ int i; /* 4 bytes */ char c; /* 1 byte */ /* 3 padding bytes */ }; struct Y { int i; /* 4 bytes */ char c; /* 1 byte */ /* 1 padding byte */ short s; /* 2 bytes */ }; struct Z { int i; /* 4 bytes */ short s; /* 2 bytes */ char c; /* 1 byte */ /* 1 padding byte */ }; const int sizeX = sizeof(struct X); /* = 12 */ const int sizeY = sizeof(struct Y); /* = 8 */ const int sizeZ = sizeof(struct Z); /* = 8 */ 

Pode-se minimizar o tamanho das estruturas, classificando membros por alinhamento (a sorting por tamanho é suficiente para isso em tipos básicos) (como a estrutura Z no exemplo acima).

NOTA IMPORTANTE: Os padrões C e C ++ indicam que o alinhamento da estrutura é definido pela implementação. Portanto, cada compilador pode optar por alinhar os dados de maneira diferente, resultando em layouts de dados diferentes e incompatíveis. Por esse motivo, ao lidar com bibliotecas que serão usadas por diferentes compiladores, é importante entender como os compiladores alinham os dados. Alguns compiladores têm configurações de linha de comando e / ou instruções #pragma especiais para alterar as configurações de alinhamento da estrutura.

Alinhamento de embalagem e byte, conforme descrito no C FAQ aqui :

É para alinhamento. Muitos processadores não podem acessar quantidades de 2 e 4 bytes (por exemplo, ints e long ints) se estiverem abarrotados de todas as maneiras.

Suponha que você tenha essa estrutura:

 struct { char a[3]; short int b; long int c; char d[3]; }; 

Agora, você pode pensar que deveria ser possível empacotar essa estrutura na memory assim:

 +-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+ 

Mas é muito mais fácil para o processador se o compilador organizar da seguinte forma:

 +-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+ 

Na versão compactada, perceba como é pelo menos um pouco difícil para você e para mim ver como os campos b e c estão por aí? Em suma, é difícil para o processador também. Portanto, a maioria dos compiladores irá preencher a estrutura (como se tivesse campos extras e invisíveis) assim:

 +-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+ 

Se você quiser que a estrutura tenha um certo tamanho com o GCC, por exemplo, use __attribute__((packed)) .

No Windows, você pode definir o alinhamento para um byte ao usar o compative cl.exe com a opção / Zp .

Geralmente, é mais fácil para a CPU acessar dados que são múltiplos de 4 (ou 8), dependendo da plataforma e também do compilador.

Então, é basicamente uma questão de alinhamento.

Você precisa ter boas razões para mudar isso.

Isso pode ser devido ao alinhamento de bytes e ao preenchimento, de modo que a estrutura saia para um número par de bytes (ou palavras) na sua plataforma. Por exemplo, em C no Linux, as seguintes 3 estruturas:

 #include "stdio.h" struct oneInt { int x; }; struct twoInts { int x; int y; }; struct someBits { int x:2; int y:6; }; int main (int argc, char** argv) { printf("oneInt=%zu\n",sizeof(struct oneInt)); printf("twoInts=%zu\n",sizeof(struct twoInts)); printf("someBits=%zu\n",sizeof(struct someBits)); return 0; } 

Já os membros cujos tamanhos (em bytes) são 4 bytes (32 bits), 8 bytes (2x 32 bits) e 1 byte (2 + 6 bits), respectivamente. O programa acima (no Linux usando o gcc) imprime os tamanhos como 4, 8 e 4 – onde a última estrutura é preenchida para que seja uma única palavra (bytes de 4 x 8 bits na minha plataforma de 32 bits).

 oneInt=4 twoInts=8 someBits=4 

Veja também:

para o Microsoft Visual C:

http://msdn.microsoft.com/pt-br/library/2e70t5y1%28v=vs.80%29.aspx

e GCC reivindicam compatibilidade com o compilador da Microsoft:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

Além das respostas anteriores, observe que, independentemente da embalagem, não há garantia de membros em C ++ . Os compiladores podem (e certamente o fazem) adicionar membros da estrutura de ponteiro e base de tabela virtuais à estrutura. Mesmo a existência da tabela virtual não é garantida pelo padrão (a implementação do mecanismo virtual não é especificada) e, portanto, pode-se concluir que tal garantia é simplesmente impossível.

Tenho certeza que a ordem dos membros é garantida em C , mas eu não contaria com isso, ao escrever um programa multi-plataforma ou cross-compiler.

O tamanho de uma estrutura é maior que a sum de suas partes por causa do que é chamado de embalagem. Um processador específico tem um tamanho de dados preferencial com o qual ele trabalha. Tamanho preferido dos processadores mais modernos se 32 bits (4 bytes). Acessar a memory quando os dados estão nesse tipo de limite é mais eficiente do que as coisas que ultrapassam esse limite de tamanho.

Por exemplo. Considere a estrutura simples:

 struct myStruct { int a; char b; int c; } data; 

Se a máquina for uma máquina de 32 bits e os dados estiverem alinhados em um limite de 32 bits, veremos um problema imediato (assumindo que não há alinhamento de estrutura). Neste exemplo, vamos supor que os dados da estrutura iniciem no endereço 1024 (0x400 – observe que os 2 bits mais baixos são zero, portanto os dados são alinhados a um limite de 32 bits). O access a dados.a funcionará bem porque começa em um limite – 0x400. O access a data.b também funcionará bem, porque está no endereço 0x404 – outro limite de 32 bits. Mas uma estrutura desalinhada colocaria data.c no endereço 0x405. Os 4 bytes de data.c estão em 0x405, 0x406, 0x407, 0x408. Em uma máquina de 32 bits, o sistema iria ler dados.c durante um ciclo de memory, mas só obteria 3 dos 4 bytes (o 4o byte está no próximo limite). Então, o sistema teria que fazer um segundo access à memory para obter o quarto byte,

Agora, se em vez de colocar data.c no endereço 0x405, o compilador preenchia a estrutura por 3 bytes e colocava data.c no endereço 0x408, então o sistema precisaria apenas de 1 ciclo para ler os dados, cortando o tempo de access a esse elemento de dados em 50%. O preenchimento troca a eficiência da memory para eficiência de processamento. Dado que os computadores podem ter grandes quantidades de memory (muitos gigabytes), os compiladores sentem que a troca (velocidade sobre o tamanho) é razoável.

Infelizmente, esse problema se torna um assassino quando você tenta enviar estruturas através de uma rede ou até mesmo gravar os dados binários em um arquivo binário. O preenchimento inserido entre elementos de uma estrutura ou class pode atrapalhar os dados enviados ao arquivo ou à rede. Para escrever código portátil (um que irá para vários compiladores diferentes), você provavelmente terá que acessar cada elemento da estrutura separadamente para garantir o “empacotamento” apropriado.

Por outro lado, diferentes compiladores têm diferentes habilidades para gerenciar o empacotamento da estrutura de dados. Por exemplo, no Visual C / C ++, o compilador suporta o comando #pragma pack. Isso permitirá ajustar o empacotamento e o alinhamento dos dados.

Por exemplo:

 #pragma pack 1 struct MyStruct { int a; char b; int c; short d; } myData; I = sizeof(myData); 

Agora eu deveria ter o comprimento de 11. Sem o pragma, eu poderia ser qualquer coisa entre 11 e 14 (e, para alguns sistemas, até 32), dependendo da embalagem padrão do compilador.

Isso pode ser feito se você definir implícita ou explicitamente o alinhamento da estrutura. Uma estrutura alinhada com 4 sempre será um múltiplo de 4 bytes, mesmo que o tamanho de seus membros seja algo que não seja um múltiplo de 4 bytes.

Além disso, uma biblioteca pode ser compilada sob x86 com ints de 32 bits e você pode estar comparando seus componentes em um processo de 64 bits que lhe daria um resultado diferente se você estivesse fazendo isso manualmente.

C99 N1256 rascunho padrão

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 O tamanho do operador :

3 Quando aplicado a um operando que possui estrutura ou tipo de união, o resultado é o número total de bytes em um object, incluindo preenchimento interno e final.

6.7.2.1 Especificadores de estrutura e união :

13 … Pode haver preenchimento sem nome dentro de um object de estrutura, mas não no início.

e:

15 Pode haver preenchimento sem nome no final de uma estrutura ou união.

O novo recurso de membro de matriz flexível C99 ( struct S {int is[];}; ) também pode afetar o preenchimento:

16 Como um caso especial, o último elemento de uma estrutura com mais de um membro nomeado pode ter um tipo de matriz incompleta; isso é chamado de membro da matriz flexível. Na maioria das situações, o membro da matriz flexível é ignorado. Em particular, o tamanho da estrutura é como se o membro da matriz flexível fosse omitido, exceto que pode ter mais preenchimento à direita do que a omissão implicaria.

O Anexo J sobre Questões de Portabilidade reitera:

O seguinte não é especificado: …

  • O valor de bytes de preenchimento ao armazenar valores em estruturas ou uniões (6.2.6.1)

Esboço padrão C ++ 11 N3337

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Tamanho de :

2 Quando aplicado a uma class, o resultado é o número de bytes em um object dessa class, incluindo qualquer preenchimento necessário para colocar objects desse tipo em uma matriz.

9.2 Membros da class :

Um ponteiro para um object struct de layout padrão, convertido adequadamente usando um reinterpret_cast, aponta para seu membro inicial (ou se esse membro é um campo de bits, depois para a unidade em que ele reside) e vice-versa. [Nota: Pode, portanto, haver preenchimento sem nome dentro de um object struct de layout padrão, mas não em seu início, conforme necessário para alcançar o alinhamento apropriado. – nota final

Eu só sei o suficiente C ++ para entender a nota 🙂

Além das outras respostas, uma estrutura pode (mas geralmente não) ter funções virtuais, caso em que o tamanho da estrutura também includeá o espaço para a vtbl.

A linguagem C deixa ao compilador alguma liberdade sobre a localização dos elementos estruturais na memory:

  • furos de memory podem aparecer entre quaisquer dois componentes e depois do último componente. Foi devido ao fato de que certos tipos de objects no computador de destino podem ser limitados pelos limites de endereçamento
  • “buracos de memory” tamanho incluído no resultado do operador sizeof. O tamanho de apenas não inclui o tamanho da matriz flexível, que está disponível em C / C ++
  • Algumas implementações da linguagem permitem controlar o layout de memory das estruturas através das opções de pragma e compilador

A linguagem C fornece alguma garantia para o programador do layout dos elementos na estrutura:

  • compiladores necessários para atribuir uma seqüência de componentes aumentando os endereços de memory
  • Endereço do primeiro componente coincide com o endereço inicial da estrutura
  • campos de bits sem nome podem ser incluídos na estrutura para os alinhamentos de endereço necessários dos elementos adjacentes

Problemas relacionados ao alinhamento dos elementos:

  • Computadores diferentes alinham as bordas dos objects de maneiras diferentes
  • Restrições diferentes na largura do campo de bits
  • Os computadores diferem em como armazenar os bytes em uma palavra (Intel 80×86 e Motorola 68000)

Como o alinhamento funciona:

  • O volume ocupado pela estrutura é calculado como o tamanho do elemento único alinhado de uma matriz de tais estruturas. A estrutura deve terminar de forma que o primeiro elemento da próxima estrutura a seguir não viole os requisitos de alinhamento

ps Informações mais detalhadas estão disponíveis aqui: “Samuel P. Harbison, Guy L.Steele CA Reference, (5.6.2 – 5.6.7)”

A idéia é que, para considerações de velocidade e cache, os operandos devem ser lidos de endereços alinhados ao seu tamanho natural. Para que isso aconteça, o compilador preenche os membros da estrutura para que o seguinte membro ou a seguinte estrutura seja alinhada.

 struct pixel { unsigned char red; // 0 unsigned char green; // 1 unsigned int alpha; // 4 (gotta skip to an aligned offset) unsigned char blue; // 8 (then skip 9 10 11) }; // next offset: 12 

A arquitetura x86 sempre conseguiu obter endereços desalinhados. No entanto, é mais lento e, quando o desalinhamento se sobrepõe a duas linhas de cache diferentes, ele despeja duas linhas de cache quando um access alinhado apenas despejaria uma.

Algumas arquiteturas, na verdade, precisam se prender a leituras e gravações desalinhadas, e as primeiras versões da arquitetura ARM (a que evoluiu para todas as CPUs móveis de hoje) … bem, elas na verdade apenas retornaram dados ruins para elas. (Eles ignoraram os bits de baixa ordem.)

Finalmente, observe que as linhas de cache podem ser arbitrariamente grandes, e o compilador não tenta adivinhar essas ou fazer uma troca de espaço versus velocidade. Em vez disso, as decisões de alinhamento são parte da ABI e representam o alinhamento mínimo que, eventualmente, preencherá uniformemente uma linha de cache.

TL; DR: o alinhamento é importante.