Quando a programação no CI considerou inestimável empacotar estruturas usando o __attribute__((__packed__))
atributo GCCs, para que eu possa converter facilmente um pedaço estruturado de memória volátil em uma matriz de bytes a serem transmitidos por um barramento, salvos no armazenamento ou aplicados a um bloco de registros. As estruturas empacotadas garantem que, quando tratadas como uma matriz de bytes, não conterão preenchimento, o que é um desperdício, um possível risco à segurança e possivelmente incompatível quando o hardware está em interface.
Não existe um padrão para empacotar estruturas que funcionem em todos os compiladores C? Se não, então sou um discrepante ao pensar que esse é um recurso crítico para a programação de sistemas? Os usuários iniciais da linguagem C não acharam necessário empacotar estruturas ou existe algum tipo de alternativa?
fonte
Respostas:
Em uma estrutura, o que importa é o deslocamento de cada membro do endereço de cada instância da estrutura. Não se trata tanto de como as coisas estão compactadas.
Uma matriz, no entanto, importa como é "empacotada". A regra em C é que cada elemento da matriz é exatamente N bytes do anterior, onde N é o número de bytes usados para armazenar esse tipo.
Mas com uma estrutura, não existe essa necessidade de uniformidade.
Aqui está um exemplo de um esquema de embalagem estranho:
A Freescale (que fabrica microcontroladores automotivos) faz um micro que possui um coprocessador da Time Processing Unit (google para eTPU ou TPU). Possui dois tamanhos de dados nativos, 8 bits e 24 bits, e lida apenas com números inteiros.
Esta estrutura:
verá cada U24 armazenado seu próprio bloco de 32 bits, mas apenas na área de endereço mais alta.
Este:
terá dois U24s armazenados em blocos adjacentes de 32 bits e o U8 será armazenado no "buraco" na frente do primeiro U24
elementA
,.Mas você pode dizer ao compilador para compactar tudo em seu próprio bloco de 32 bits, se desejar; é mais caro na RAM, mas usa menos instruções para acessos.
"empacotar" não significa "empacotar firmemente" - significa apenas algum esquema para organizar elementos de uma estrutura no deslocamento.
Não existe um esquema genérico, ele depende do compilador + da arquitetura.
fonte
struct b
para moverelementC
antes que qualquer um dos outros elementos, então não é um compilador conformidade C. Elemento rearranjo não é permitido em CComo você mencionou
__attribute__((__packed__))
, presumo que sua intenção é eliminar todos os preenchimentos dentro de umstruct
(faça com que cada membro tenha um alinhamento de 1 byte).... E a resposta é não". O preenchimento e o alinhamento de dados em relação a uma estrutura (e matrizes contíguas de estruturas na pilha ou pilha) existem por um motivo importante. Em muitas máquinas, o acesso desalinhado à memória pode levar a uma penalidade de desempenho potencialmente significativa (embora diminua em alguns hardwares mais novos). Em alguns casos raros, o acesso desalinhado à memória leva a um erro de barramento irrecuperável (pode até travar todo o sistema operacional).
Como o padrão C é focado na portabilidade, faz pouco sentido ter uma maneira padrão de eliminar todo o preenchimento em uma estrutura e permitir apenas que campos arbitrários sejam desalinhados, pois isso pode potencialmente arriscar tornar o código C não portátil.
A maneira mais segura e portátil de enviar esses dados para uma fonte externa de uma maneira que elimine todo o preenchimento é serializar de / para fluxos de bytes, em vez de apenas tentar enviar o conteúdo da memória não processada do seu
structs
. Isso também evita que seu programa sofra penalidades de desempenho fora desse contexto de serialização e também permite adicionar livremente novos campos a umstruct
sem desperdiçar e danificar todo o software. Também lhe dará espaço para lidar com endianness e coisas assim, se isso se tornar uma preocupação.Existe uma maneira de eliminar todo o preenchimento sem alcançar diretivas específicas do compilador, embora isso seja aplicável apenas se a ordem relativa entre os campos não for importante. Dado algo parecido com isto:
... precisamos do preenchimento para acesso alinhado à memória em relação ao endereço da estrutura que contém esses campos, da seguinte forma:
... onde
.
indica preenchimento. Todosx
devem se alinhar a um limite de 8 bytes para desempenho (e às vezes até comportamento correto).Você pode eliminar o preenchimento de maneira portátil usando uma representação SoA (estrutura da matriz) da seguinte forma (vamos supor que precisamos de 8
Foo
instâncias):Nós efetivamente demolimos a estrutura. Nesse caso, a representação da memória fica assim:
... e isto:
... não há mais sobrecarga de preenchimento e sem envolver acesso desalinhado à memória, pois não estamos mais acessando esses campos de dados como um deslocamento de um endereço de estrutura, mas como um deslocamento de um endereço de base para o que é efetivamente uma matriz.
Isso também traz o bônus de ser mais rápido no acesso seqüencial como resultado de menos dados consumidos (preenchimento irrelevante na mistura para diminuir a taxa relevante de consumo de dados da máquina) e também um potencial para o compilador vetorizar o processamento de maneira muito trivial .
A desvantagem é que é uma PITA codificar. Também é potencialmente menos eficiente para acesso aleatório com o passo mais amplo entre os campos, onde frequentemente os representantes de AoS ou AoSoA se saem melhor. Mas essa é uma maneira padrão de eliminar o preenchimento e embalar as coisas o mais firmemente possível, sem estragar o alinhamento de tudo.
fonte
Nem todas as arquiteturas são iguais, basta ativar a opção de 32 bits em um módulo e ver o que acontece ao usar o mesmo código fonte e o mesmo compilador. A ordem dos bytes é outra limitação bem conhecida. Jogue na representação de ponto flutuante e os problemas pioram. O uso do Packing para enviar dados binários não é portátil. Para padronizá-lo para que ele fosse praticamente utilizável, você precisaria redefinir a especificação da linguagem C.
Embora comum, usar o Pack para enviar dados binários é uma má idéia se você deseja segurança, portabilidade ou longevidade dos dados. Com que frequência você lê um blob binário de uma fonte no seu programa. Com que frequência você verifica se todos os valores são sensatos, se um hacker ou uma alteração de programa não "conseguiu" os dados? No momento em que você codificou uma rotina de verificação, você também pode estar usando rotinas de importação e exportação.
fonte
Uma alternativa muito comum é "padding nomeado":
Isso faz supor a estrutura não será preenchido a 8 bytes.
fonte