União e problema de empacotamento struct
Pergunta
Eu estou escrevendo algum software onde cada bit deve ser exato (que é para o CPU) para __packed é muito importante.
typedef union{
uint32_t raw;
struct{
unsigned int present:1;
unsigned int rw:1;
unsigned int user:1;
unsigned int dirty:1;
unsigned int free:7;
unsigned int frame:20;
} __packed;
}__packed page_union_t;
que é a minha estrutura e união. Ele não funciona no entanto:
page_union_t p; //.....
//This:
p.frame=trg_page;
p.user=user;
p.rw=rw;
p.present=present;
//and this:
p.raw=trg_page<<12 | user<<2 | rw<<1 | present;
devem criar o mesmo uint32. Mas eles não criam a mesma coisa.
Existe algo que eu não posso ver o que está errado com a minha união?
Solução
A sua estrutura tem apenas 31 bits
Outras dicas
AFAIK, a ordem em que os bits na struct são armazenados é indefinido pelo padrão C99 (e o padrão C89 também). Muito provavelmente, os bits são na ordem inversa do que você esperava.
Você deveria ter mostrado o resultado que você tem, bem como o resultado que você espera - seria nos ajudar com o diagnóstico. O compilador que você usa e a plataforma que você rodar em também poderia ser significativo.
No MacOS X 10.4.11 (PowerPC G4), este código:
#include <inttypes.h>
#include <stdio.h>
typedef union
{
uint32_t raw;
struct
{
unsigned int present:1;
unsigned int rw:1;
unsigned int user:1;
unsigned int dirty:1;
unsigned int free:7;
unsigned int frame:20;
};
} page_union_t;
int main(void)
{
page_union_t p = { .raw = 0 }; //.....
unsigned trg_page = 0xA5A5A;
unsigned user = 1;
unsigned rw = 1;
unsigned present = 1;
p.frame = trg_page;
p.user = user;
p.rw = rw;
p.present = present;
printf("p.raw = 0x%08X\n", p.raw);
p.raw = trg_page<<12 | user<<2 | rw<<1 | present;
printf("p.raw = 0x%08X\n", p.raw);
p.raw <<= 1;
printf("p.raw = 0x%08X\n", p.raw);
return(0);
}
produz os resultados mostrado:
p.raw = 0xE014B4B4
p.raw = 0xA5A5A007
p.raw = 0x4B4B400E
Com a ordem dos campos invertidos, o resultado é mais quase explicável:
#include <inttypes.h>
#include <stdio.h>
typedef union
{
uint32_t raw;
struct
{
unsigned int frame:20;
unsigned int free:7;
unsigned int dirty:1;
unsigned int user:1;
unsigned int rw:1;
unsigned int present:1;
};
} page_union_t;
int main(void)
{
page_union_t p = { .raw = 0 }; //.....
unsigned trg_page = 0xA5A5A;
unsigned user = 1;
unsigned rw = 1;
unsigned present = 1;
p.frame = trg_page;
p.user = user;
p.rw = rw;
p.present = present;
printf("p.raw = 0x%08X\n", p.raw);
p.raw = trg_page<<12 | user<<2 | rw<<1 | present;
printf("p.raw = 0x%08X\n", p.raw);
p.raw <<= 1;
printf("p.raw = 0x%08X\n", p.raw);
return(0);
}
Isto dá o resultado:
p.raw = 0xA5A5A00E
p.raw = 0xA5A5A007
p.raw = 0x4B4B400E
O primeiro resultado tem um E como o último digito hexadecimal porque o bit menos significativo não é utilizada, porque a estrutura de campo de bit tem apenas 31 bits definidos ..
Se a posição exata de bits importa, sua aposta mais segura é a embalagem explícita e descompactação da estrutura em uma matriz de char não assinado. Qualquer outra coisa que é muito dependente de implementação.
Para referência para qualquer pessoa que pode achar isso, tente o atributo embalado:
struct __attribute__((packed)){
}
Você não mencione que você está limpando os bits da estrutura de antemão, você tem certeza que você não está terminando com pedaços de lixo que sobraram no primeiro caso?
// maybe try this
page_union_t p = {0};