Layout da memória estrutural em C

86

Tenho experiência em C #. Sou muito um novato em uma linguagem de baixo nível como C.

Em C #, structa memória de é definida pelo compilador por padrão. O compilador pode reordenar os campos de dados ou preencher bits adicionais entre os campos implicitamente. Então, eu tive que especificar algum atributo especial para substituir esse comportamento para o layout exato.

AFAIK, C não reordena ou alinha o layout de memória de um structpor padrão. No entanto, ouvi dizer que há uma pequena exceção que é muito difícil de encontrar.

Qual é o comportamento do layout de memória do C? O que deve ser reordenado / alinhado e não?

eonil
fonte

Respostas:

111

Em C, o compilador pode ditar algum alinhamento para cada tipo primitivo. Normalmente, o alinhamento é o tamanho do tipo. Mas é totalmente específico da implementação.

Bytes de preenchimento são introduzidos para que cada objeto seja alinhado corretamente. Reordenar não é permitido.

Possivelmente, todo compilador moderno remotamente implementa o #pragma packque permite o controle sobre o preenchimento e deixa para o programador cumprir a ABI. (No entanto, é estritamente fora do padrão.)

De C99 §6.7.2.1:

12 Cada membro de campo que não é de bits de uma estrutura ou objeto de união é alinhado de uma maneira definida pela implementação apropriada ao seu tipo.

13 Dentro de um objeto de estrutura, os membros do campo que não são de bits e as unidades nas quais os campos de bits residem têm endereços que aumentam na ordem em que são declarados. Um ponteiro para um objeto de estrutura, adequadamente convertido, aponta para seu membro inicial (ou se esse membro for um campo de bits, então para a unidade na qual ele reside) e vice-versa. Pode haver preenchimento sem nome dentro de um objeto de estrutura, mas não em seu início.

Potatoswatter
fonte
1
Alguns compiladores (por exemplo, GCC) implementam o mesmo efeito, #pragma packmas com um controle mais refinado sobre a semântica.
Chris Lutz
21
Estou surpreso ao ver um voto negativo. Alguém pode apontar o erro?
Potatoswatter
2
C11 também _Alignas.
idmean
117

É específico da implementação, mas na prática a regra (na ausência #pragma packou semelhante) é:

  • Os membros da estrutura são armazenados na ordem em que são declarados. (Isso é exigido pelo padrão C99, conforme mencionado aqui anteriormente.)
  • Se necessário, o preenchimento é adicionado antes de cada membro da estrutura, para garantir o alinhamento correto.
  • Cada tipo primitivo T requer um alinhamento de sizeof(T)bytes.

Portanto, dada a seguinte estrutura:

  • ch1 está no deslocamento 0
  • um byte de preenchimento é inserido para alinhar ...
  • s no deslocamento 2
  • ch2 está no deslocamento 4, imediatamente após s
  • 3 bytes de preenchimento são inseridos para alinhar ...
  • ll no deslocamento 8
  • i está no deslocamento 16, logo após 11
  • 4 bytes de preenchimento são adicionados no final para que a estrutura geral seja um múltiplo de 8 bytes. Eu verifiquei isso em um sistema de 64 bits: sistemas de 32 bits podem permitir que as estruturas tenham alinhamento de 4 bytes.

Portanto, sizeof(ST)é de 24.

Ele pode ser reduzido para 16 bytes reorganizando os membros para evitar o preenchimento:

dan04
fonte
3
Se necessário, o preenchimento é adicionado antes ... Mais como depois. É melhor adicionar um charmembro final ao seu exemplo.
Deduplicator
9
Um tipo primitivo não requer necessariamente um alinhamento de sizeof(T)bytes. Por exemplo, doubleem arquiteturas comuns de 32 bits é de 8 bytes, mas geralmente requer apenas alinhamento de 4 bytes . Além disso, o preenchimento no final da estrutura apenas preenche o alinhamento do membro da estrutura mais largo. Por exemplo, uma estrutura de 3 variáveis ​​char pode não ter preenchimento.
Matt
1
@ dan04, seria uma boa prática criar o layout de estruturas na ordem decrescente de sizeof (T). Haveria alguma desvantagem em fazer isso?
RohitMat
11

Você pode começar lendo o artigo da wikipedia sobre alinhamento de estrutura de dados para obter uma melhor compreensão do alinhamento de dados.

Do artigo da wikipedia :

Alinhamento de dados significa colocar os dados em um deslocamento de memória igual a algum múltiplo do tamanho da palavra, o que aumenta o desempenho do sistema devido à maneira como a CPU lida com a memória. Para alinhar os dados, pode ser necessário inserir alguns bytes sem sentido entre o final da última estrutura de dados e o início da próxima, que é o preenchimento da estrutura de dados.

De 6.54.8 Pragmas de Estrutura-Empacotamento da documentação GCC:

Para compatibilidade com compiladores Microsoft Windows, o GCC suporta um conjunto de diretivas #pragma que mudam o alinhamento máximo de membros de estruturas (exceto campos de bits de largura zero), uniões e classes definidas posteriormente. O valor n abaixo sempre deve ser uma pequena potência de dois e especifica o novo alinhamento em bytes.

  1. #pragma pack(n) simplesmente define o novo alinhamento.
  2. #pragma pack() define o alinhamento para aquele que estava em vigor quando a compilação começou (veja também a opção de linha de comando -fpack-struct [=] veja Opções de Geração de Código).
  3. #pragma pack(push[,n]) coloca a configuração de alinhamento atual em uma pilha interna e, opcionalmente, define o novo alinhamento.
  4. #pragma pack(pop)restaura a configuração de alinhamento para aquela salva no topo da pilha interna (e remove essa entrada da pilha). Observe que #pragma pack([n])não influencia esta pilha interna; assim, é possível ter #pragma pack(push) seguido por várias #pragma pack(n) instâncias e finalizado por uma única #pragma pack(pop).

Alguns destinos, por exemplo, i386 e powerpc, suportam o ms_struct #pragmaque apresenta uma estrutura conforme documentada __attribute__ ((ms_struct)).

  1. #pragma ms_struct on ativa o layout para estruturas declaradas.
  2. #pragma ms_struct off desativa o layout para estruturas declaradas.
  3. #pragma ms_struct reset volta ao layout padrão.
jschmier
fonte
Obrigado pelo cuidado. Modifiquei a pergunta conforme você me orientou.
eonil