Estructura de diseño de memoria en C

85

Tengo experiencia en C #. Soy muy novato en un lenguaje de bajo nivel como C.

En C #, structel compilador distribuye la memoria de forma predeterminada. El compilador puede reordenar los campos de datos o rellenar bits adicionales entre campos de forma implícita. Entonces, tuve que especificar algún atributo especial para anular este comportamiento para un diseño exacto.

AFAIK, C no reordena ni alinea el diseño de la memoria de a structde forma predeterminada. Sin embargo, escuché que hay una pequeña excepción que es muy difícil de encontrar.

¿Cuál es el comportamiento del diseño de memoria de C? ¿Qué se debe reordenar / alinear y no?

eonil
fuente

Respuestas:

110

En C, el compilador puede dictar alguna alineación para cada tipo primitivo. Normalmente, la alineación es el tamaño del tipo. Pero es completamente específico de la implementación.

Se introducen bytes de relleno para que cada objeto esté correctamente alineado. No se permite reordenar.

Posiblemente, todos los compiladores modernos de forma remota implementan lo #pragma packque permite el control sobre el relleno y deja que el programador cumpla con la ABI. (Sin embargo, es estrictamente no estándar).

De C99 §6.7.2.1:

12 Cada miembro que no es de campo de bits de una estructura u objeto de unión se alinea de una manera definida por la implementación apropiada para su tipo.

13 Dentro de un objeto de estructura, los miembros que no son campos de bits y las unidades en las que residen los campos de bits tienen direcciones que aumentan en el orden en que se declaran. Un puntero a un objeto de estructura, convenientemente convertido, apunta a su miembro inicial (o si ese miembro es un campo de bits, entonces a la unidad en la que reside), y viceversa. Puede haber relleno sin nombre dentro de un objeto de estructura, pero no al principio.

Potatoswatter
fuente
1
Algunos compiladores (es decir, GCC) implementan el mismo efecto #pragma packpero con un control más detallado sobre la semántica.
Chris Lutz
21
Me sorprende ver un voto negativo. ¿Alguien puede señalar el error?
Potatoswatter
2
C11 también tiene _Alignas.
idmean
117

Es específico de la implementación, pero en la práctica la regla (en ausencia #pragma packo similar) es:

  • Los miembros de la estructura se almacenan en el orden en que se declaran. (Esto es requerido por el estándar C99, como se mencionó anteriormente).
  • Si es necesario, se agrega relleno antes de cada miembro de la estructura para garantizar una alineación correcta.
  • Cada tipo de primitiva T requiere una alineación de sizeof(T)bytes.

Entonces, dada la siguiente estructura:

struct ST
{
   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
};
  • ch1 está en offset 0
  • se inserta un byte de relleno para alinear ...
  • s en el desplazamiento 2
  • ch2 está en el desplazamiento 4, inmediatamente después de s
  • Se insertan 3 bytes de relleno para alinear ...
  • ll en el desplazamiento 8
  • i está en el desplazamiento 16, justo después de ll
  • Se agregan 4 bytes de relleno al final para que la estructura general sea un múltiplo de 8 bytes. Verifiqué esto en un sistema de 64 bits: los sistemas de 32 bits pueden permitir que las estructuras tengan una alineación de 4 bytes.

También lo sizeof(ST)es 24.

Se puede reducir a 16 bytes reorganizando los miembros para evitar el relleno:

struct ST
{
   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
} ST;
dan04
fuente
3
Si es necesario, se agrega relleno antes ... Más como después. Mejor agregue un charmiembro final a su ejemplo.
Deduplicador
9
Un tipo primitivo no requiere necesariamente una alineación de sizeof(T)bytes. Por ejemplo, doubleen arquitecturas comunes de 32 bits es de 8 bytes, pero a menudo solo requiere una alineación de 4 bytes . Además, el relleno al final de la estructura solo se adapta a la alineación del miembro de estructura más ancho. Por ejemplo, una estructura de 3 variables de caracteres podría no tener relleno.
Matt
1
@ dan04, ¿sería una buena práctica diseñar estructuras en orden descendente de tamaño de (T)? ¿Habría inconvenientes en hacer esto?
RohitMat
11

Puede comenzar leyendo el artículo de Wikipedia sobre alineación de la estructura de datos para comprender mejor la alineación de datos.

Del artículo de wikipedia :

La alineación de datos significa colocar los datos en un desplazamiento de memoria igual a un múltiplo del tamaño de la palabra, lo que aumenta el rendimiento del sistema debido a la forma en que la CPU maneja la memoria. Para alinear los datos, puede ser necesario insertar algunos bytes sin sentido entre el final de la última estructura de datos y el comienzo de la siguiente, que es el relleno de la estructura de datos.

De 6.54.8 Pragmas de empaquetado de estructuras de la documentación de GCC:

Para la compatibilidad con los compiladores de Microsoft Windows, GCC admite un conjunto de directivas #pragma que cambian la alineación máxima de miembros de estructuras (que no sean campos de bits de ancho cero), uniones y clases definidas posteriormente. El valor de n que aparece a continuación siempre debe ser una pequeña potencia de dos y especifica la nueva alineación en bytes.

  1. #pragma pack(n) simplemente establece la nueva alineación.
  2. #pragma pack() establece la alineación con la que estaba en vigor cuando se inició la compilación (consulte también la opción de línea de comandos -fpack-struct [=] consulte Opciones de generación de código).
  3. #pragma pack(push[,n]) empuja la configuración de alineación actual en una pila interna y luego, opcionalmente, establece la nueva alineación.
  4. #pragma pack(pop)restaura la configuración de alineación a la guardada en la parte superior de la pila interna (y elimina esa entrada de pila). Tenga en cuenta que #pragma pack([n])no influye en esta pila interna; así se puede haber #pragma pack(push) seguido por múltiples #pragma pack(n) instancias y finalizado por una sola #pragma pack(pop).

Algunos destinos, por ejemplo, i386 y powerpc, admiten ms_struct, #pragmaque establece una estructura como la documentada __attribute__ ((ms_struct)).

  1. #pragma ms_struct on enciende el diseño de las estructuras declaradas.
  2. #pragma ms_struct off desactiva el diseño de las estructuras declaradas.
  3. #pragma ms_struct reset vuelve al diseño predeterminado.
jschmier
fuente
Gracias por preocuparte. Modifiqué la pregunta mientras me guiaste.
eonil