Dados C ++ alinhamento dos membros e matriz Packing
-
16-09-2019 - |
Pergunta
Durante uma revisão de código eu me deparei com um código que define uma estrutura simples como o seguinte:
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
}
Por outro lado, uma matriz desses objetos é definido:
foo listOfFoos[SOME_NUM];
Mais tarde, as estruturas são matéria-copiado em um buffer:
memcpy(pBuff,listOfFoos,3*SOME_NUM);
Este código baseia-se nas premissas que:... A) O tamanho de foo é 3, e sem preenchimento é aplicado, e b) Uma matriz de estes objectos é embalado sem preenchimento entre eles
Eu tentei com GNU em duas plataformas (64B RedHat, Solaris 9), e funcionou em ambos.
As suposições acima são válidas? Se não, em que condições (por exemplo, mudança no OS / compilador) podem eles falham?
Solução
Uma matriz de objectos é necessário para ser contígua, de modo que nunca é preenchimento entre os objectos, apesar de preenchimento pode ser adicionada à extremidade de um objecto (a produção de quase o mesmo efeito).
Uma vez que você está trabalhando com carvão de, os pressupostos são provavelmente direito mais frequentemente do que não, mas o C ++ padrão certamente não garante isso. Um compilador diferente, ou mesmo apenas uma mudança nas bandeiras passados ??para o compilador atual poderia resultar em estofamento sendo inserido entre os elementos da estrutura ou após o último elemento da estrutura, ou ambos.
Outras dicas
Seria definitivamente ser mais seguro para fazer:
sizeof(foo) * SOME_NUM
Se você copiar sua matriz como este que você deve usar
memcpy(pBuff,listOfFoos,sizeof(listOfFoos));
Isso sempre vai trabalhar enquanto você alocado pBuff para o mesmo tamanho. Desta forma, você está fazendo nenhuma suposição sobre o preenchimento e alinhamento em tudo.
A maioria dos compiladores alinhar uma estrutura ou classe para o alinhamento necessário da maior tipo incluído. No seu caso de caracteres que significa que não há alinhamento e preenchimento, mas se você adicionar um curto, por exemplo, sua classe seria 6 bytes grande com um byte de preenchimento adicionado entre o último caractere e seu short.
Penso que a razão que isso funciona porque todos os campos da estrutura são CHAR qual se alinhar. Se existe, pelo menos, um campo que não alinha 1, o alinhamento da estrutura / classe não será um (a vontade alinhamento depende da ordem do campo e alinhamento).
Vamos ver alguns exemplos:
#include <stdio.h>
#include <stddef.h>
typedef struct {
unsigned char a;
unsigned char b;
unsigned char c;
} Foo;
typedef struct {
unsigned short i;
unsigned char a;
unsigned char b;
unsigned char c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;
#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )
int main(void) {
printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}
Quando executado, o resultado é:
Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2
Você pode ver que Bar e F_B tem alinhamento 2 de modo que seu campo i será devidamente alinhados. Você também pode ver que Tamanho do Bar é 6 e não 5 . Da mesma forma, o tamanho do B_F (5 de Bar) é 30 e não 25 .
Então, se você é um código rígido em vez de sizeof(...)
, você vai ter um problema aqui.
Espero que isso ajude.
Tudo se resume ao alinhamento de memória. máquinas de 32 bits típicos ler ou escrever 4 bytes de memória por tentativa. Esta estrutura está a salvo de problemas, porque ele cai sob que 4 bytes facilmente, sem problemas confundindo preenchimento.
Agora, se a estrutura era como tal:
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
unsigned int i;
unsigned int j;
}
Sua lógica colegas de trabalho provavelmente levaria a
memcpy(pBuff,listOfFoos,11*SOME_NUM);
(3 de Char = 3 bytes, 2 ints = 2 * 4 bytes, de modo 3 + 8)
Infelizmente, devido a acolchoar a estrutura realmente ocupa 12 bytes. Isso é porque você não pode caber três caractere do e um int em que a palavra de 4 bytes, e por isso há um byte de espaço acolchoado lá que empurra o int em seu próprio texto. Isto torna-se mais e mais um problema dos mais diversos tipos de dados tornam-se.
Para situações onde o material como este é usado, e eu não posso evitá-lo, eu tento fazer a ruptura de compilação quando os pressupostos já não detêm. Eu uso algo como o seguinte (ou Boost.StaticAssert se a situação o permitir):
static_assert(sizeof(foo) <= 3);
// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp) static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line) static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
Eu teria sido seguro e substituiu o número mágico 3 com um reckon sizeof(foo)
I.
Meu palpite é que o código otimizado para arquiteturas de processador futuro provavelmente irá introduzir alguma forma de preenchimento.
E tentando rastrear esse tipo de erro é uma dor real!
Como já foi dito, usando sizeof (foo) é uma aposta mais segura. Alguns compiladores (especialmente os esotéricos do mundo incorporado) irá adicionar um cabeçalho de 4 bytes de classes. Outros podem fazer truques de memória de alinhamento funk, dependendo de suas configurações do compilador.
Para uma plataforma mainstream, provavelmente você está certo, mas não é uma garantia.
Há ainda pode ser um problema com sizeof () quando você está passando os dados entre dois computadores. Em um deles, o código pode compilar com estofamento e na outra, sem, caso em que sizeof () daria resultados diferentes. Se os dados de matriz é transmitida de um computador para o outro ele vai ser mal interpretada, porque os elementos da matriz não será encontrado onde esperado. Uma solução é ter certeza de que #pragma pack (1) é usado sempre que possível, mas isso pode não ser suficiente para as matrizes. Melhor é prever o problema e utilização estofo para um múltiplo de 8 bits por elemento de matriz.