Tengo una estructura definida que se usa para mensajes enviados a través de dos interfaces diferentes. Uno de ellos requiere alineación de 32 bits, pero necesito minimizar el espacio que ocupan. Esencialmente, estoy tratando de empaquetar en bytes las estructuras, es decir, #pragma pack(1) , pero me aseguro de que la estructura resultante tenga una longitud múltiplo de 32 bits. Estoy usando un compilador cruzado de brazo gcc para un procesador M3 de 32 bits. Lo que creo que quiero hacer es algo como esto:
#pragma pack(1) typedef struct my_type_t { uint32_t someVal; uint8_t anotherVal; uint8_t reserved[<??>]; } #pragma pack() donde <??> asegura que el tamaño de my_type_t sea divisible por 4 bytes, pero sin codificar el tamaño del relleno. Puedo hacer algo como esto:
#pragma pack(1) typedef struct wrapper_t { my_type_t m; uint8_t reserved[sizeof(my_type_t) + 4 - (sizeof(my_type_t) % 4)] } #pragma pack()pero me gustaría evitar eso.
En última instancia, lo que debo hacer es copiar esto en un búfer que sea direccionable de 32 bits, como:
static my_type_t t; //If it makes a difference, this will be declared statically in my C source file ... memcpy(bufferPtr, (uint32_t*)&t, sizeof(t)) //or however I should do this Miré el __attribute__((align(N))) , que me da la dirección de memoria alineada de 32 bits para la estructura, pero no la empaqueta en bytes. Estoy confundido acerca de cómo (o si) esto se puede combinar con pack(1) .
Mi pregunta es esta:
¿Cuál es la forma correcta de declarar estas estructuras para que pueda minimizar su huella en la memoria pero que me permita copiarlas/configurarlas en incrementos de 4 bytes con un puntero de 32 bits sin firmar? (Hay un montón de estos tipos de tamaño y contenido arbitrario). Si mi enfoque anterior de combinar pack y el relleno es totalmente incorrecto, con gusto tomaré alternativas.
Editar:
Algunas limitaciones: No tengo control sobre una de las interfaces. Está esperando marcos llenos de bytes. El otro lado son registros mapeados en memoria direccionable de 32 bits. Tengo 64k de memoria para todo el ejecutable, y estoy limitado en las bibliotecas, etc. que puedo traer. Ya hay una gran cantidad de optimización de espacio que he tenido que hacer.
La estructura en esta pregunta fue solo para explicar mi pregunta. Tengo numerosos mensajes de contenido variable a los que esto se aplica.
No puedo hablar por el compilador y la arquitectura específicos que está utilizando, pero espero que lo siguiente sea suficiente:
typedef struct { uint32_t x; uint8_t y; } my_type_t;La estructura normalmente tiene la misma alineación que su campo más grande, y eso incluye agregar el relleno necesario al final.
my_type_t +---------------+ | x | +---+-----------+ | y | [padding] | +---+-----------+ |<-- 32 bits -->|Esto se hace para que los campos estén correctamente alineados cuando tenga una matriz de ellos.
my_type_t my_array[2]; my_array[1].x = 123; // Needs to be properly aligned.Lo anterior supone que tiene control sobre el orden de los campos para obtener la mejor eficiencia de espacio, ya que se basa en que el compilador alinee los campos individuales. Pero esas suposiciones se pueden eliminar usando atributos GCC.
typedef struct { uint8_t x; uint32_t y; uint8_t z; } __attribute__((packed)) // Remove interfield padding. __attribute__((aligned(4))) // Set alignment and add tail padding. my_type_t;Esto produce esto:
my_type_t +---+-----------+ | x | y +---+---+-------+ | z | [pad] | +---+---+-------+ |<-- 32 bits -->| El atributo packed evita que se agregue relleno entre campos, pero alinear la estructura con un límite de 32 bits fuerza la alineación que desea. Esto tiene el efecto secundario de agregar relleno final para que pueda tener una variedad de estas estructuras de manera segura.
A medida que usa gcc, necesita usar uno de los atributos.
Ejemplo + demostración.
#define PACKED __attribute__((packed)) #define ALIGN(n) __attribute__((aligned(n))) typedef struct { uint8_t anotherVal; uint32_t someVal; }PACKED my_type_t; my_type_t t = {1, 5}; ALIGN(64) my_type_t t1 = {1, 5}; ALIGN(512) my_type_t t2 = {2, 6}; int main() { printf("%p, %p, %p", (void *)&t, (void *)&t1, (void *)&t2); }Resultado:
0x404400, 0x404440, 0x404600Sugiero combinar #pragma pack con alignas :
#include <stdalign.h> #include <stdint.h> typedef struct { #pragma pack(1) alignas(4) struct { // requires 2+1+2 bytes but is aligned to even 4:s uint16_t someVal; // +0 uint8_t anotherVal; // +2 uint16_t foo; // +3 (would be 4 without packing) }; #pragma pack() } my_type_t; La struct interna anónima facilita el acceso como antes:
int main() { my_type_t y; y.someVal = 10; y.anotherVal = 'a'; y.foo = 20; printf("%zu\n", (char*)&y.someVal - (char*)&y.someVal); // 0 printf("%zu\n", (char*)&y.anotherVal - (char*)&y.someVal); // 2 printf("%zu\n", (char*)&y.foo - (char*)&y.someVal); // 3 my_type_t x[2]; printf("%zu\n", (char*)&x[1] - (char*)&x[0]); // 8 bytes diff } Si desea poder tomar el tamaño de los datos reales que my_type_t sizeof para enviarlos), puede nombrar la struct interna (lo que hace que acceder a los campos sea un poco más engorroso):
#pragma pack(1) typedef struct { uint16_t someVal; uint8_t anotherVal; uint16_t foo; } inner; #pragma pack() typedef struct { alignas(4) inner i; } my_type_t; Ahora tendría que mencionar i para acceder a los campos, pero tiene la ventaja de que puede tomar sizeof y obtener 5 (en este ejemplo):
int main() { my_type_t y; printf("%zu %zu\n", sizeof y, alignof(y)); // 8 4 printf("%zu\n", sizeof yi); // 5 (the actual data) }