C / C ++: Forzar orden de campo de bits y alineación

87

Leí que el orden de los campos de bits dentro de una estructura es específico de la plataforma. ¿Qué pasa si utilizo diferentes opciones de empaquetado específicas del compilador, esto garantizará que los datos se almacenen en el orden correcto tal como están escritos? Por ejemplo:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

En un procesador Intel con el compilador GCC, los campos se colocaron en la memoria tal como se muestran. Message.versionfueron los primeros 3 bits en el búfer y Message.typesiguieron. Si encuentro opciones de empaquetado de estructuras equivalentes para varios compiladores, ¿será multiplataforma?

Dewald
fuente
17
Dado que un búfer es un conjunto de bytes, no de bits, "los primeros 3 bits del búfer" no es un concepto preciso. ¿Consideraría que los 3 bits de menor orden del primer byte son los primeros 3 bits, o los 3 bits de mayor orden?
caf
2
Al transitar por la red, "Los primeros 3 bits del búfer" resultan muy bien definidos.
Joshua
2
@Joshua IIRC, Ethernet transmite primero el bit menos significativo de cada byte (razón por la cual el bit de transmisión está donde está).
tc.
Cuando dices "portátil" y "multiplataforma", ¿a qué te refieres? El ejecutable accederá correctamente a la orden independientemente del sistema operativo de destino, o el código se compilará independientemente de la cadena de herramientas.
Garet Claborn

Respuestas:

103

No, no será completamente portátil. Las opciones de empaquetado para estructuras son extensiones y en sí mismas no son completamente portátiles. Además de eso, C99 §6.7.2.1, párrafo 10 dice: "El orden de asignación de campos de bits dentro de una unidad (de orden superior a orden inferior o de orden inferior a orden superior) está definido por la implementación".

Incluso un solo compilador podría distribuir el campo de bits de manera diferente dependiendo del endianness de la plataforma de destino, por ejemplo.

Stephen Canon
fuente
Sí, el GCC, por ejemplo, señala específicamente que los campos de bits se organizan según el ABI, no la implementación. Por lo tanto, quedarse en un solo compilador no es suficiente para garantizar el pedido. La arquitectura también debe revisarse. En realidad, es una pesadilla para la portabilidad.
underscore_d
10
¿Por qué el estándar C no garantizaba un pedido de campos de bits?
Aaron Campbell
7
Es difícil definir de forma coherente y portátil el "orden" de los bits dentro de los bytes, y mucho menos el orden de los bits que pueden cruzar los límites de los bytes. Cualquier definición con la que se establezca no coincidirá con una cantidad considerable de práctica existente.
Stephen Canon
2
implementaiton-defined permite la optimización específica de la plataforma. En algunas plataformas, el relleno entre los campos de bits puede mejorar el acceso, imagine cuatro campos de siete bits en un int de 32 bits: alinearlos en cada octavo bit es una mejora significativa para las plataformas que tienen lecturas de bytes.
peterchen
hace packedcumplir el pedido: stackoverflow.com/questions/1756811/… cómo hacer cumplir el pedido de bits: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
45

Los campos de bits varían mucho de un compilador a otro, lo siento.

Con GCC, las máquinas Big Endian colocan primero el extremo grande de los bits y las máquinas Little Endian colocan primero el extremo pequeño de los bits.

K&R dice "Los elementos de estructuras de campo adyacente [bit-] se empaquetan en unidades de almacenamiento dependientes de la implementación en una dirección dependiente de la implementación. Cuando un campo que sigue a otro campo no encaja ... puede dividirse entre unidades o la unidad puede ser padded. Un campo sin nombre de ancho 0 fuerza este relleno ... "

Por lo tanto, si necesita un diseño binario independiente de la máquina, debe hacerlo usted mismo.

Esta última declaración también se aplica a los campos que no son de bits debido al relleno; sin embargo, todos los compiladores parecen tener alguna forma de forzar el empaquetado de bytes de una estructura, como veo que ya descubrió para GCC.

Joshua
fuente
¿K&R realmente se considera una referencia útil, dado que fue una pre-estandarización y (supongo?) Probablemente haya sido reemplazada en muchas áreas?
underscore_d
1
Mi K&R es posterior a ANSI.
Joshua
1
Ahora que es vergonzoso: no me di cuenta de que habían publicado una revisión posterior a ANSI. ¡Culpa mía!
underscore_d
35

Deben evitarse los campos de bits; no son muy portátiles entre compiladores, incluso para la misma plataforma. del estándar C99 6.7.2.1/10 - "Especificadores de estructura y unión" (hay una redacción similar en el estándar C90):

Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande como para contener un campo de bits. Si queda suficiente espacio, un campo de bits que sigue inmediatamente a otro campo de bits en una estructura se empaquetará en bits adyacentes de la misma unidad. Si queda espacio insuficiente, se define la implementación si un campo de bits que no encaja se coloca en la siguiente unidad o se superpone a las unidades adyacentes. El orden de asignación de los campos de bits dentro de una unidad (de orden superior a orden inferior o de orden inferior a orden superior) está definido por la implementación. La alineación de la unidad de almacenamiento direccionable no está especificada.

No puede garantizar si un campo de bits 'abarcará' un límite int o no y no puede especificar si un campo de bits comienza en el extremo inferior del int o en el extremo superior del int (esto es independiente de si el procesador es big-endian o little-endian).

Prefiere las máscaras de bits. Utilice líneas (o incluso macros) para configurar, borrar y probar los bits.

Michael Burr
fuente
2
El orden de los campos de bits se puede determinar en tiempo de compilación.
Greg A. Woods
9
Además, los campos de bits son muy preferidos cuando se trata de indicadores de bits que no tienen representación externa fuera del programa (es decir, en el disco o en los registros o en la memoria a la que acceden otros programas, etc.).
Greg A. Woods
1
@ GregA.Woods: Si este es realmente el caso, proporcione una respuesta que describa cómo. No pude encontrar nada más que tu comentario al buscarlo en Google ...
mozzbozz
1
@ GregA.Woods: Lo siento, debería haber escrito a qué comentario me referí. Quise decir: Dices que "El orden de los campos de bits se puede determinar en tiempo de compilación". No sé nada al respecto y cómo hacerlo.
mozzbozz
2
@mozzbozz Eche un vistazo a planix.com/~woods/projects/wsg2000.cy busque las definiciones y el uso de _BIT_FIELDS_LTOHy_BIT_FIELDS_HTOL
Greg A. Woods
11

endianness están hablando de órdenes de bytes, no de órdenes de bits. Hoy en día , es 99% seguro que las órdenes de bits son fijas. Sin embargo, cuando se utilizan campos de bits, se debe tener en cuenta la endianidad. Vea el ejemplo a continuación.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
pierrotlefou
fuente
6
La salida de ayb indica que endianness todavía está hablando de órdenes de bits Y órdenes de bytes.
Programador de Windows
maravilloso ejemplo con problemas de ordenación de bits y ordenación de bytes
Jonathan
1
¿Realmente compiló y ejecutó el código? Los valores para "a" y "b" no me parecen lógicos: básicamente estás diciendo que el compilador intercambiará los nibbles dentro de un byte debido al endianness. En el caso de "d", los endiannes no deberían afectar el orden de bytes dentro de las matrices de caracteres (asumiendo que char tiene 1 byte de longitud); si el compilador hiciera eso, no podríamos iterar a través de una matriz usando punteros. Si, por otro lado, ha utilizado una matriz de dos enteros de 16 bits, por ejemplo: uint16 data [] = {0x1234,0x5678}; entonces d definitivamente sería 0x7856 en sistemas little endian.
Krauss
6

La mayoría de las veces, probablemente, pero no apueste la granja, porque si se equivoca, perderá mucho.

Si realmente, realmente necesita tener información binaria idéntica, deberá crear campos de bits con máscaras de bits; por ejemplo, usa un código corto sin firmar (16 bits) para Mensaje y luego crea cosas como versionMask = 0xE000 para representar los tres bits superiores.

Existe un problema similar con la alineación dentro de las estructuras. Por ejemplo, las CPU Sparc, PowerPC y 680x0 son todas big-endian, y el valor predeterminado común para los compiladores Sparc y PowerPC es alinear los miembros de la estructura en límites de 4 bytes. Sin embargo, un compilador que usé para 680x0 solo se alineó en límites de 2 bytes, ¡y no había ninguna opción para cambiar la alineación!

Entonces, para algunas estructuras, los tamaños en Sparc y PowerPC son idénticos, pero más pequeños en 680x0, y algunos de los miembros están en diferentes desplazamientos de memoria dentro de la estructura.

Este fue un problema con un proyecto en el que trabajé, porque un proceso de servidor que se ejecutaba en Sparc consultaba a un cliente y descubría que era big-endian, y suponía que simplemente podía lanzar estructuras binarias en la red y el cliente podía hacer frente. Y eso funcionó bien en los clientes de PowerPC y se estrelló enormemente en los clientes de 680x0. No escribí el código y me tomó bastante tiempo encontrar el problema. Pero fue fácil de arreglar una vez que lo hice.

Bob Murphy
fuente
1

Gracias @BenVoigt por su útil comentario a partir de

No, fueron creados para ahorrar memoria.

Fuente de Linux hace uso de un campo de bits para que coincida con una estructura externa: /usr/include/linux/ip.h tiene el código para el primer byte de un datagrama IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Sin embargo, a la luz de su comentario, renuncio a intentar que esto funcione para el campo de bits multibyte frag_off .

Duncan Roe
fuente
-9

Por supuesto, la mejor respuesta es usar una clase que lea / escriba campos de bits como una secuencia. El uso de la estructura de campo de bits C simplemente no está garantizado. Sin mencionar que se considera poco profesional / vago / estúpido usar esto en la codificación del mundo real.

99999999
fuente
5
Creo que es incorrecto afirmar que es estúpido usar campos de bits, ya que proporciona una forma muy limpia de representar registros de hardware, que fue creado para modelar, en C.
trondd
13
@trondd: No, fueron creados para ahorrar memoria. Los campos de bits no están destinados a mapearse con estructuras de datos externas, como registros de hardware mapeados en memoria, protocolos de red o formatos de archivo. Si estuvieran destinados a mapear estructuras de datos externas, el orden de empaque se habría estandarizado.
Ben Voigt
2
El uso de bits ahorra memoria. El uso de campos de bits aumenta la legibilidad. Usar menos memoria es más rápido. El uso de bits permite operaciones atómicas más complejas. En nuestras aplicaciones en el mundo real, existe la necesidad de rendimiento y operaciones atómicas complejas. Esta respuesta no funcionaría para nosotros.
johnnycrash
@BenVoigt probablemente sea cierto, pero si un programador está dispuesto a confirmar que el orden de su compilador / ABI coincide con lo que necesita y sacrifica la portabilidad rápida en consecuencia, entonces ciertamente puede cumplir ese papel. En cuanto a 9 *, ¿qué masa autorizada de "codificadores del mundo real" considera que todo uso de campos de bits es "poco profesional / vago / estúpido" y dónde lo declararon?
underscore_d
2
Usar menos memoria no siempre es más rápido; A menudo es más eficiente usar más memoria y reducir las operaciones posteriores a la lectura, y el modo de procesador / procesador puede hacer que eso sea aún más cierto.
Dave Newton