2010-10-12 10 views
6

Para aclarar mi pregunta, vamos a empezar con un programa de ejemplo:¿Qué está haciendo VC++ al empacar bitfields?

#include <stdio.h> 

#pragma pack(push,1) 
struct cc { 
    unsigned int a : 3; 
    unsigned int b : 16; 
    unsigned int c : 1; 
    unsigned int d : 1; 
    unsigned int e : 1; 
    unsigned int f : 1; 
    unsigned int g : 1; 
    unsigned int h : 1; 
    unsigned int i : 6; 
    unsigned int j : 6; 
    unsigned int k : 4; 
    unsigned int l : 15; 
}; 
#pragma pack(pop) 

struct cc c; 

int main(int argc, char **argv) 

{ printf("%d\n",sizeof(c)); 
} 

La salida es "8", lo que significa que los 56 bits (7 bytes) que quieren hacer las maletas están siendo empaquetados en 8 bytes, aparentemente desperdiciando un byte completo. Curiosidad acerca de cómo el compilador estaba colocando estos bits en la memoria, he intentado escribir valores específicos a &c, por ejemplo:

int main (int argc, char ** argv)

{ 
unsigned long long int* pint = &c; 
*pint = 0xFFFFFFFF; 
printf("c.a = %d", c.a); 
... 
printf("c.l = %d", c.l); 
} 

Como era de esperar, el uso de x86_64 visual Studio 2010, ocurre lo siguiente:

*pint = 0x00000000 000000FF : 

c[0].a = 7 
c[0].b = 1 
c[0].c = 1 
c[0].d = 1 
c[0].e = 1 
c[0].f = 1 
c[0].g = 0 
c[0].h = 0 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 

*pint = 0x00000000 0000FF00 : 

c[0].a = 0 
c[0].b = 0 
c[0].c = 0 
c[0].d = 0 
c[0].e = 0 
c[0].f = 0 
c[0].g = 1 
c[0].h = 127 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 


*pint = 0x00000000 00FF0000 : 

c[0].a = 0 
c[0].b = 0 
c[0].c = 0 
c[0].d = 0 
c[0].e = 0 
c[0].f = 0 
c[0].g = 0 
c[0].h = 32640 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 

etc.

Forget portabilidad por un momento y suponer que usted se preocupa por una CPU, un compilador y un entorno de ejecución. ¿Por qué no puede VC++ empacar esta estructura en 7 bytes? ¿Es una cosa de longitud de palabra? El MSDN docs en #pragma pack dice "la alineación de un miembro estará en un límite que es un múltiplo de n [1 en mi caso] o un múltiplo del tamaño del miembro, el que sea más pequeño". ¿Alguien puede darme una idea de por qué obtengo un tamaño de 8 y no de 7?

+2

La documentación dice "... estará en un límite que es ..."; Sin embargo, no puedo encontrar dónde dice nada sobre una garantía de tamaño. –

Respuesta

5

MSVC++ siempre asigna al menos una unidad de memoria que corresponde al tipo que utilizó para su campo de bits. Usaste unsigned int, lo que significa que un unsigned int se asigna inicialmente, y otro unsigned int se asigna cuando el primero se agota. No hay forma de forzar a MSVC++ a recortar la parte no utilizada del segundo unsigned int.

Básicamente, MSVC++ interpreta su unsigned int como una forma de expresar los requisitos de alineación para toda la estructura.

Uso tipos más pequeños para sus campos de bits (unsigned short y unsigned char) y reagrupar los campos de bits de forma que llenen la unidad asignada en su totalidad - de esa manera usted debería ser capaz de empacar las cosas lo más fuerte posible.

+0

En este caso, no puede guardar nada si necesita los 15 y 16 bits, prefiere terminar con al menos 9 Bytes porque ya usa 56 Bits. – Vinzenz

+0

No ... Con este consejo puedo empacarlo en 7 bytes (gracias AndreyT). No tengo idea de lo que quieres decir con 9 bytes. – Rooke

+0

@Rooke: Tenga en cuenta que 'bits de caracteres sin signo: 10' es válido, pero probablemente no es lo que quiere decir (solo almacenará valores de hasta 8 bits y reservará 2 bits adicionales para el relleno). Es decir. es de vital importancia reagrupar los campos de bits para que cada campo de bits encaje en la unidad asignada. –

3

Los bitfields se almacenan en el tipo que usted defina. Dado que está utilizando unsigned int, y no cabe en un solo unsigned int, entonces el compilador debe usar un segundo entero y almacenar los últimos 24 bits en ese último entero.

+0

no, esto es incorrecto. el compilador puede almacenar campos de bits en cualquier tipo que desee. – Abyx

+0

@Abyx esto va en contra de cualquier otra respuesta y comentario aquí. Si tiene algo para apoyar su afirmación, nos gustaría verlo. – Spencer

1

Bueno, estás utilizando unsigned int que pasa a ser de 32 bits en este caso. El siguiente límite (para caber en el campo de bits) para unsigned int es 64 Bit => 8 Bytes.

0

pst es correcto. Los miembros están alineados en límites de 1 byte (o más pequeños, dado que es un campo de bits). La estructura general tiene el tamaño 8 y está alineada en un límite de 8 bytes. Esto cumple tanto con la opción estándar como con la pack. Los documentos nunca dicen que no habrá relleno al final.

+0

El problema no es con el relleno al final, sino con el hecho de que los campos de bits están empaquetados dentro de unidades del tipo campo de bit. La estructura no tiene relleno, solo dos miembros 'unsigned int'. –

+0

@David, la norma dice (§6.7.2.1), "Un campo de bits se interpreta como un tipo entero con signo o sin signo que consiste en el número especificado de bits . [...] Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande para contener un campo de bit ". Así que no creo que 'unsigned int' signifique que realmente debe usar' unsigned int' como la unidad de almacenamiento, solo un tipo sin firmar con suficientes bits. Además, se permite que los campos de bit crucen los límites de la unidad de almacenamiento. Entonces creo que hay relleno al final. –

+0

Por cierto, ¿qué estándar estás buscando? Ni el estándar actual de C++ ni el C++ 0x FCD tienen la sección 6.7.2.1. –

0

Para dar otro interesante ejemplo de lo que está sucediendo, considere el caso en el que desea empaquetar una estructura que cruza un límite de tipo. P.ej.

struct state { 
    unsigned int cost  : 24; 
    unsigned int back  : 21; 
    unsigned int a  : 1; 
    unsigned int b  : 1; 
    unsigned int c  : 1; 
}; 

Esta estructura no se puede empaquetar en 6 bytes utilizando MSVC hasta donde yo sé.Sin embargo, podemos obtener el efecto de empaquetamiento deseado dividiendo los primeros dos campos:

struct state_packed { 
    unsigned short cost_1 : 16; 
    unsigned char cost_2 : 8; 
    unsigned short back_1 : 16; 
    unsigned char back_2 : 5; 
    unsigned char a  : 1; 
    unsigned char b  : 1; 
    unsigned char c  : 1; 
}; 

Esto se puede empaquetar en 6 bytes. Sin embargo, acceder al campo de costo original es extremadamente incómodo y desagradable. Un método consiste en convertir un puntero a una estructura state_packed ficticia especializada:

struct state_cost { 
    unsigned int cost  : 24; 
    unsigned int junk  : 8; 
}; 

state_packed sc; 
state_packed *p_sc = &sc; 

sc.a = 1; 
(*(struct state_cost *)p_sc).cost = 12345; 
sc.b = 1; 

Si alguien conoce una forma más elegante de hacer esto, me gustaría saber!

Cuestiones relacionadas