Recentemente, li Type Layout e fiquei surpreso com a descrição de repr primitivo em enums. Consulte Representação primitiva de enums com campos e Combinando representações primitivas de enums com campos e#[repr(C)]
. Minha pergunta é: essas duas representações não são equivalentes?
Mais concretamente, considere esta enumeração:
enum WithFields {
A(u16),
B(u8, u16),
C(u32, u32),
}
Se fosse anotado com repr(u8)
, teria a mesma representação UnionWithTag
abaixo:
#[repr(C)]
union UnionWithTag {
a: VariantA,
b: VariantB,
c: VariantC,
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Tag {
A,
B,
C,
}
#[derive(Copy, Clone)]
#[repr(C)]
struct VariantA(Tag, u16);
#[derive(Copy, Clone)]
#[repr(C)]
struct VariantB(Tag, u8, u16);
#[derive(Copy, Clone)]
#[repr(C)]
struct VariantC(Tag, u32, u32);
Se fosse anotado com repr(C, u8)
, teria a mesma representação TaggedUnion
abaixo:
#[repr(C)]
struct TaggedUnion {
tag: Tag,
payload: Union,
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Tag {
A,
B,
C,
}
#[repr(C)]
pub union Union {
a: PayloadA,
b: PayloadB,
c: PayloadC,
}
#[derive(Copy, Clone)]
#[repr(C)]
struct PayloadA(u16);
#[derive(Copy, Clone)]
#[repr(C)]
struct PayloadB(u8, u16);
#[derive(Copy, Clone)]
#[repr(C)]
struct PayloadC(u32, u32);
Não são TaggedUnion
e UnionWithTag
idênticos no layout?
Pode parecer que eles seriam idênticos, mas a
repr(C, u8)
versão acabará com mais preenchimento do que arepr(u8)
versão. Considere uma versão simplificada da enumeração, apenas com aB
variante:como
repr(u8)
:e como
repr(C, u8)
:Trabalharemos de baixo para cima para calcular os tamanhos gerais de
TaggedUnion
eUnionWithTag
.PayloadB
requer um byte de preenchimento após ou8
campo para alinhar corretamente ou16
campo.VariantB
todos os campos se encaixam sem preenchimento (au8
tag e ou8
campo alinham perfeitamente ou16
campo)TaggedUnion
é umau8
tag seguida por (uma união, contendo apenas)PayloadB
, que tem align 2. Isso força um byte de preenchimento após a tag, para alinhá-la corretamente. No geral, tamanho 6, alinhamento 2. 2 bytes de preenchimento no geral.UnionWithTag
is (uma união, contendo apenas)VariantB
, que tem tamanho 4, align 2. Nenhum preenchimento é usado.Esta não é realmente uma grande exploração de por que os layouts não são equivalentes, mas é uma prova por contradição eficaz de que eles realmente não são equivalentes.
Em ambos os exemplos,
VariantC
/PayloadC
tem um alinhamento de 4, enquanto o restante dosVariant
/Payload
s tem um alinhamento de 2.No
TaggedUnion
exemplo,Union
tem alinhamento 4, que é o alinhamento máximo de seus membros. Portanto,TaggedUnion
deve-se colocar três bytes de preenchimento entre a tag u8 e a união, em todos os casos, para satisfazer o pior caso dePayloadC
.No entanto, os
Variant
s têm mais flexibilidade permitida a eles.VariantA
O membro u16 de precisa apenas de um alinhamento de 2, portanto, precisa apenas de um byte de preenchimento entre a tag e seus dados.VariantB
pode empacotar firmemente seus dados sem qualquer preenchimento.VariantC
ainda precisa de 3 bytes de preenchimento. O padding extra, porém, volta quando eles são colocados noUnion
, mas está localizado no final da união.Portanto, como os s marcados internamente
Variant
sabem que incluirão uma tag, eles podem mover seus dados para mais perto da tag, mas comoPayload
os s podem existir como uma estrutura separada fora de sua tag, eles precisam ser alinhados, tornando o layout de duas abordagens -incompatível.