Determinando o alinhamento de estruturas C / C ++ em relação a seus membros

O alinhamento de um tipo de estrutura pode ser encontrado se os alinhamentos dos membros da estrutura forem conhecidos?

Por exemplo. para:

struct S { a_t a; b_t b; c_t c[]; }; 

é o alinhamento de S = max (alinhamento_de (a), alinhamento_ de (b), alinhamento_de (c))?

Pesquisando na internet, descobri que “para tipos estruturados, o maior requisito de alinhamento de qualquer um dos elementos determina o alinhamento da estrutura” (em O que todo programador deve saber sobre memory ), mas não consegui encontrar nada semelhante no padrão rascunho mais exatamente).


Editado: Muito obrigado por todas as respostas, especialmente a Robert Gamble que forneceu uma resposta realmente boa para a pergunta original e para as outras que contribuíram.

Em resumo:

Para garantir os requisitos de alinhamento para os membros da estrutura, o alinhamento de uma estrutura deve ser pelo menos tão rigoroso quanto o alinhamento de seu membro mais restrito.

Quanto à determinação do alinhamento da estrutura, algumas opções foram apresentadas e com um pouco de pesquisa, foi isso que eu encontrei:

  • c ++ std :: tr1 :: alignment_of
    • não padrão ainda, mas perto (relatório técnico 1), deve estar no C ++ 0x
    • as seguintes restrições estão presentes no último rascunho: Pré-condição: T deve ser um tipo completo, um tipo de referência ou uma matriz de limite desconhecido, mas não deve ser um tipo de function ou (possivelmente qualificado pelo cv) nulo.
      • isso significa que meu caso de uso apresentado com o array flexível C99 não funcionará (isso não é surpreendente, pois os arrays flexíveis não são padrão c ++)
    • no último rascunho do c ++, ele é definido nos termos de uma nova palavra-chave – alignas (isso tem o mesmo requisito de tipo completo)
    • na minha opinião, deve c ++ padrão sempre suporta matrizes flexíveis C99, o requisito poderia ser relaxado (o alinhamento da estrutura com a matriz flexível não deve mudar com base no número de elementos da matriz)
  • c ++ boost :: alinhamento_do
    • principalmente um substituto tr1
    • parece ser especializado para void e retorna 0 nesse caso (isso é proibido no draft c ++)
    • Nota dos desenvolvedores: estritamente falando, você deve confiar apenas no valor de ALIGNOF (T) sendo um múltiplo do verdadeiro alinhamento de T, embora na prática ele compute o valor correto em todos os casos que conhecemos.
    • Eu não sei se isso funciona com matrizes flexíveis, deve (pode não funcionar em geral, isso resolve compilador intrínseco na minha plataforma, então eu não sei como ele vai se comportar no caso geral)
  • Andrew Top apresentou uma solução modelo simples para calcular o alinhamento nas respostas
    • isso parece estar muito próximo do que o boost está fazendo (o boost também retornará o tamanho do object como o alinhamento se for menor que o alinhamento calculado até onde eu possa ver), então provavelmente o mesmo aviso se aplica
    • isso funciona com matrizes flexíveis
  • use o Windbg.exe para descobrir o alinhamento de um símbolo
    • não compilar tempo, compilador específico, não testou
  • usando offsetof na estrutura anônima contendo o tipo
    • veja as respostas, não confiáveis, não portáveis ​​com c ++ não-POD
  • intrinsics compilador, por exemplo. MSVC __alignof
    • trabalha com matrizes flexíveis
    • A palavra-chave alignof está no último rascunho do c ++

Se quisermos usar a solução “padrão”, estamos limitados a std :: tr1 :: alignment_of, mas isso não funcionará se você misturar seu código c ++ com os arrays flexíveis do c99.

Como eu vejo, há apenas 1 solução – use o antigo struct hack:

 struct S { a_t a; b_t b; c_t c[1]; // "has" more than 1 member, strictly speaking this is undefined behavior in both c and c++ when used this way }; 

Os padrões divergentes c e c ++ e suas diferenças crescentes são lamentáveis ​​neste caso (e em todos os outros casos).


Outra questão interessante é (se não podemos descobrir o alinhamento de uma estrutura de maneira portátil) qual é o requisito de alinhamento mais rígido possível. Existem algumas soluções que eu encontrei:

  • boost (internamente) usa uma união de vários tipos e usa o boost :: alignment_of nele
  • o último rascunho do c ++ contém std :: aligned_storage
    • O valor do alinhamento padrão deve ser o requisito de alinhamento mais rigoroso para qualquer tipo de object C ++ cujo tamanho não seja maior que Len
      • então o valor std::alignment_of< std::aligned_storage>::value deve nos fornecer o alinhamento máximo
      • rascunho apenas, ainda não padrão (se alguma vez), tr1::aligned_storage não tem essa propriedade

Qualquer pensamento sobre isso também seria apreciado.

Desmarcou temporariamente a resposta aceite para obter mais visibilidade e comentários sobre as novas subquestões

Há dois conceitos intimamente relacionados aqui:

  1. O alinhamento requerido pelo processador para acessar um object em particular
  2. O alinhamento que o compilador realmente usa para colocar objects na memory

Para garantir os requisitos de alinhamento para os membros da estrutura, o alinhamento de uma estrutura deve ser pelo menos tão rigoroso quanto o alinhamento de seu membro mais restrito. Eu não acho que isso seja explicitamente explicitado no padrão, mas pode ser inferido a partir dos seguintes fatos (que são soletrados individualmente no padrão):

  • Estruturas podem ter preenchimento entre seus membros (e no final)
  • Arrays não podem ter preenchimento entre seus elementos
  • Você pode criar uma matriz de qualquer tipo de estrutura

Se o alinhamento de uma estrutura não fosse pelo menos tão estrito quanto cada um de seus membros, você não seria capaz de criar uma matriz de estruturas, uma vez que alguns membros da estrutura alguns elementos não seriam alinhados adequadamente.

Agora, o compilador deve garantir um alinhamento mínimo para a estrutura com base nos requisitos de alinhamento de seus membros, mas também pode alinhar os objects de maneira mais rigorosa do que a exigida. Isso geralmente é feito por motivos de desempenho. Por exemplo, muitos processadores modernos permitem access a inteiros de 32 bits em qualquer alinhamento, mas os accesss podem ser significativamente mais lentos se não estiverem alinhados em um limite de 4 bytes.

Não existe uma maneira portátil de determinar o alinhamento imposto pelo processador para qualquer tipo dado, porque isso não é exposto pela linguagem, embora, como o compilador obviamente conhece os requisitos de alinhamento do processador de destino, possa expor essas informações como uma extensão.

Também não existe uma maneira portátil (pelo menos em C) para determinar como um compilador alinhará um object, embora muitos compiladores tenham opções para fornecer algum nível de controle sobre o alinhamento.

Eu escrevi este tipo de código de traço para determinar o alinhamento de qualquer tipo (baseado nas regras do compilador já discutidas). Você pode achar útil:

 template  class Traits { public: struct AlignmentFinder { char a; T b; }; enum {AlignmentOf = sizeof(AlignmentFinder) - sizeof(T)}; }; 

Então agora você pode ir:

 std::cout << "The alignment of structure S is: " << Traits::AlignmentOf << std::endl; 

A macro a seguir retornará o requisito de alinhamento de qualquer tipo (mesmo que seja uma estrutura):

 #define TYPE_ALIGNMENT( t ) offsetof( struct { char x; t test; }, test ) 

Nota: Eu provavelmente peguei emprestada essa idéia de um header da Microsoft em algum ponto do meu passado …


Edit: como Robert Gamble aponta nos comentários, não é garantido que esta macro funcione. De fato, certamente não funcionará muito bem se o compilador estiver configurado para empacotar elementos em estruturas. Então, se você decidir usá-lo, use-o com caucanvas.

Alguns compiladores têm uma extensão que permite obter o alinhamento de um tipo (por exemplo, começando com VS2002, o MSVC tem um __alignof() intrínseco). Aqueles devem ser usados ​​quando disponíveis.

Como os outros mencionaram, sua implementação depende. O Visual Studio 2005 usa 8 bytes como o alinhamento de estrutura padrão. Internamente, os itens são alinhados por seu tamanho – um float tem alinhamento de 4 bytes, um duplo usa 8, etc.

Você pode replace o comportamento com #pragma pack. O GCC (e a maioria dos compiladores) tem opções de compilador ou pragmas semelhantes.

É possível assumir um alinhamento de estrutura se você souber mais detalhes sobre as opções do compilador que estão em uso. Por exemplo, #pragma pack (1) forçará o alinhamento no nível de bytes para alguns compiladores.

Nota lateral: Eu sei que a questão era sobre alinhamento, mas uma questão secundária é preenchimento. Para programação embutida, dados binários e assim por diante – Em geral, não assuma nada sobre o alinhamento da estrutura, se possível. Em vez disso, use o preenchimento explícito, se necessário, nas estruturas. Eu tive casos em que era impossível duplicar o alinhamento exato usado em um compilador para um compilador em uma plataforma diferente sem adicionar elementos de preenchimento. Tinha a ver com o alinhamento das estruturas dentro das estruturas, de modo que a adição de elementos de preenchimento o fixava.

Se você quiser descobrir isso para um caso específico no Windows, abra o windbg:

 Windbg.exe -z \path\to\somemodule.dll -y \path\to\symbols 

Então corra:

 dt somemodule!CSomeType 

Eu não acho que o layout da memory é garantido de alguma forma em qualquer padrão C. Isso é muito dependente do fornecedor e do arquiteto. Pode haver maneiras de fazer isso que funcionem em 90% dos casos, mas eles não são padrão.

Eu ficaria muito feliz em ser provado errado, embora =)

Concordo principalmente com Paul Betts, Ryan e Dan. Realmente, depende do desenvolvedor, você pode manter o alinhamento padrão do symanic, o qual Robert observou (a explicação de Robert é apenas o comportamento padrão e não é exigida), ou você pode configurar o alinhamento que quiser / Zp [# #].

O que isto significa é que se você tem um typedef com floats ‘, long double’s, uchar’s etc … vários sortimentos de matrizes incluídos. Então tem outro tipo que tem alguns desses membros de forma estranha, e um único byte, depois outro membro ímpar, ele simplesmente será alinhado em qualquer preferência que o arquivo make / solution definir.

Como observado anteriormente, usando o comando dt do windbg em tempo de execução, você pode descobrir como o compilador definiu a estrutura na memory.

Você também pode usar qualquer ferramenta de leitura de pdb como dia2dump para extrair esta informação do pdb estaticamente.

Modificado do blog de Peeter Joot

O alinhamento da estrutura C é baseado no tipo nativo de tamanho maior na estrutura, pelo menos em geral (uma exceção é algo como usar um inteiro de 64 bits no win32, em que apenas o alinhamento de 32 bits é necessário).

Se você tiver apenas chars e matrizes de chars, uma vez que você adicionar um int, ele acabará iniciando em um limite de 4 bytes (com possível preenchimento oculto antes do membro int). Além disso, se a estrutura não for um múltiplo de sizeof (int), o preenchimento oculto será adicionado no final. Mesma coisa para os tipos curto e de 64 bits.

Exemplo:

 struct blah1 { char x ; char y[2] ; }; 

sizeof (blah1) == 3

 struct blah1plusShort { char x ; char y[2] ; // <<< hidden one byte inserted by the compiler here // <<< z will start on a 2 byte boundary (if beginning of struct is aligned). short z ; char w ; // <<< hidden one byte tail pad inserted by the compiler. // <<< the total struct size is a multiple of the biggest element. // <<< This ensures alignment if used in an array. }; 

sizeof (blah1plusShort) == 8

Eu li essa resposta depois de 8 anos e sinto que a resposta aceita do @Robert é geralmente correta, mas matematicamente errada.

Para garantir os requisitos de alinhamento para membros da estrutura, o alinhamento de uma estrutura deve ser pelo menos tão restrito quanto o mínimo múltiplo comum do alinhamento de seus membros . Considere um exemplo estranho, em que os requisitos de alinhamento dos membros são 4 e 10; Nesse caso, o alinhamento da estrutura é LCM (4, 10), que é 20, e não 10. Naturalmente, é estranho ver plataformas com tal requisito de alinhamento que não seja uma potência de 2 e, portanto, para todos os casos práticos , o alinhamento da estrutura é igual ao alinhamento máximo de seus membros.

A razão para isso é que, somente se o endereço da estrutura começar com o LCM de seus alinhamentos de membros, o alinhamento de todos os membros pode ser satisfeito e o preenchimento entre os membros e o final da estrutura é independente do endereço inicial .

Atualização: Como apontado por @chqrlie no comentário, o padrão C não permite os valores ímpares do alinhamento. No entanto, esta resposta ainda prova porque o alinhamento da estrutura é o máximo de seus alinhamentos de membro, apenas porque o máximo é o mínimo múltiplo comum e, portanto, os membros estão sempre alinhados em relação ao endereço múltiplo comum.

Intereting Posts