Na estrutura C, por que packed,aligned
parece fazer preenchimento?
Eu tenho que ler vários bytes MSB/LSB de um dispositivo remoto usando i2c. Como todos os dados do dispositivo são bytes, eu uso uint8_t
o que representa exatamente 8 bits, ou seja, 1 byte.
Agora preciso ter certeza de que a estrutura está packed
(ou seja, não há "buraco" entre os elementos na estrutura), a menos que eu não leia os dados à medida que são gerados no dispositivo remoto.
Em seguida, adicionei aligned
desempenho (ou seja, permitir que a CPU leia a estrutura com acesso mínimo à memória).
Conforme explicado aqui Preenchimento e empacotamento da estrutura , o preenchimento alinha os membros da estrutura aos limites de endereço "naturais" e o empacotamento evita o preenchimento.
Este teste simples me surpreendeu:
$ gcc --version
gcc (Debian 10.2.1-6) 10.2.1 20210110
$ cat dumb.c
#include <stdio.h>
#include <stdint.h> // uint8_t
struct struct1 {
uint8_t msb;
} __attribute__((packed));
struct struct2 {
uint8_t msb;
} __attribute__((packed,aligned(4)));
struct struct3 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed));
struct struct4 {
uint8_t msb;
uint8_t lsb;
} __attribute__((packed,aligned(4)));
int main(void) {
struct struct1 s1;
printf("sizeof(s1) %zu\n", sizeof(s1));
struct struct2 s2;
printf("sizeof(s2) %zu\n", sizeof(s2));
struct struct3 s3;
printf("sizeof(s3) %zu\n", sizeof(s3));
struct struct4 s4;
printf("sizeof(s4) %zu\n", sizeof(s4));
return 0;
}
$ gcc -o dumb dumb.c
$ ./dumb
sizeof(s1) 1
sizeof(s2) 4
sizeof(s3) 2
sizeof(s4) 4
aligned
parece "preencher o final da estrutura": isso é esperado?
- Eu esperava
s2
começar com um endereço de memória múltiplo de 4 bytes, mas ter tamanho 1. - Eu esperava
s4
começar com um endereço de memória múltiplo de 4 bytes, mas ter tamanho 2.
Eu li esses bytes de um dispositivo remoto para que cada byte seja transferido usando i2c do dispositivo para o PC: agora para desempenho, é melhor ir packed
apenas para evitar "preenchimento extra no final", ou é melhor ir packed,aligned
transferindo e lendo bytes extras "preenchidos no final" não utilizados cada vez que há uma transferência?
O
aligned
atributo em umastruct
declaração significa que qualquer instância da estrutura deve começar em um endereço alinhado.Isso também significa que se você tiver uma matriz de estruturas, a estrutura poderá exigir preenchimento final para que cada membro da matriz esteja alinhado corretamente.
Embora o
packed
atributo minimize qualquer preenchimento usado na estrutura, esse preenchimento não pode necessariamente ser eliminado completamente quando usado em conjunto comaligned
.Então, por exemplo, se você tivesse esta declaração:
Se você não tivesse preenchimento final,
s2[1]
não estaria alinhado corretamente.Porque o requisito de alinhamento de um tipo de dados deve dividir seu tamanho. Caso contrário, você não poderia formar arrays desse tipo. Se você forçar um requisito de alinhamento específico, o compilador poderá precisar ajustar o tamanho (adicionando preenchimento).
Uma maneira possivelmente mais natural de fazer isso seria ler seus pares MSB/LSB em um único
uint16_t
, que você pode ter certeza de que não possui preenchimento interno. E como você descreve os pares dessa maneira, suponho que você queira interpretar os pares como números de 16 bits, o que pode ser convenientemente alcançado por meio dantohs()
função.É muito provável que seja uma otimização prematura. Qualquer atraso resultante do desalinhamento provavelmente será completamente sobrecarregado pela sobrecarga de E/S.
Mais ou menos. Esse é o propósito normal do preenchimento entre membros em layouts de estrutura, mas não é uma característica inerente desse preenchimento, nem é o único tipo de local para preenchimento.
Não exatamente. O significado preciso das diretivas de controle de embalagem varia entre as implementações C, mas você está usando o GCC, cuja definição é:
além disso,
... em que contexto,
Então não, isso não se refere diretamente ao preenchimento. Forçar o requisito de alinhamento para membros (não-bitfield) para 1 byte resulta no layout deles sem qualquer preenchimento inicial, mas isso não diz nada sobre se algum preenchimento segue.
Sim, conforme já discutido. E isso não entra em conflito com a definição de
packed
.Novamente, um tipo com requisito de alinhamento de 4 terá um tamanho múltiplo de 4, portanto, suas expectativas estavam incorretas. No entanto, acho que o GCC permite alinhar variáveis individuais; portanto, se você realmente deseja o que descreve, poderá alcançá-lo, para essas variáveis específicas, movendo o
aligned
atributo dos tipos para as variáveis correspondentes:Mas, novamente, acho que você está se preocupando demais com isso. Mover bytes em sua interface i2c consumirá uma grande quantidade de tempo aqui.
Quem disse que você leria qualquer coisa em qualquer preenchimento de estrutura? Você não deveria. Se você estiver lendo bytes, um par de cada vez, não o fará. Se você estiver lendo sequências de vários pares de bytes em uma única operação, não poderá ter preenchimento. Mas, novamente, você parece estar tornando as coisas muito mais difíceis do que precisam. Eu pularia as estruturas e usaria type
uint16_t
para os pares de bytes. Especialmente se você estiver lendo muitos deles juntos; nesse caso, você usaria um arquivouint16_t[]
. Supondo que você queira interpretar cada par como um número de 16 bits, pós-processe cada um apósntohs()
a leitura, antes de fazer qualquer outra coisa.Se o resultado não for rápido o suficiente, crie um perfil para determinar onde está o gargalo e trabalhe nisso.