Sinergia en C/C++
Según la RAE Sinergia es : «Acción de dos o más causas cuyo efecto es superior a la suma de los efectos individuales.». Vamos que la suma del todo es mayor que el de las partes. Recientemente me he topado (otra vez) con este efecto programando, cómo somos viejos conocidos os lo voy a presentar. Se llama padding y suele venir de serie en las estructuras y clases de C o C++.
Veamos un ejemplo:
typedef struct { float f1; float f2; } Struct1; typedef struct { char c; float f; } Struct2;
Tenemos dos estructuras Struct1 y Struct2, una tiene dos floats y la otra un float y un char. La primera, deberÃa ocupar 8 bytes (2×4 bytes) y la segunda 5 bytes (4+1 bytes). Pero el caso es que el compilador elige que no sea asÃ, comprobémoslo con este pequeño programa en C:
#include <stdio.h> int main(int argc, char **argv) { Struct1 test1[10]; Struct2 test2[10]; printf("\n-- Test with Struct1 (2 floats) -----------------\n"); printf("Size of the structure Struct1 = %u bytes (%u)\n", sizeof(Struct1), sizeof(float)+sizeof(float) ); printf("Struct1[0] = %p\n", test1); printf("Struct1[1] = %p\n", test1 + 1); printf("Distance = %u\n", (unsigned int)(test1+1) - (unsigned int)(test1)); printf("\n-- Test width Strruct2 ( 1 float + 1 char)-------\n"); printf("Size of the structure Struct2 = %u bytes (%u)\n", sizeof(Struct2), sizeof(float)+sizeof(char)); printf("Struct2[0] = %p\n", test2); printf("Struct2[1] = %p\n", test2 + 1); printf("Distance = %u\n", (unsigned int)(test2+1) - (unsigned int)(test2)); printf("\n-- Final Test -----------------------------------\n"); printf(" (sizeof(Struct1) == sizeof(Struct2)) --> %s\n", ( sizeof(Struct1) == sizeof(Struct2) ) ? "true" : "false"); return 0; }
El resultado de ejecutarlo es el siguiente:
pplux@Matrix:~/tmp$ gcc test.c -Wall pplux@Matrix:~/tmp$ ./a.out -- Test with Struct1 (2 floats) ----------------- Size of the structure Struct1 = 8 bytes (8) Struct1[0] = 0xbf9f9478 Struct1[1] = 0xbf9f9480 Distance = 8 -- Test width Strruct2 ( 1 float + 1 char)------- Size of the structure Struct2 = 8 bytes (5) Struct2[0] = 0xbf9f9428 Struct2[1] = 0xbf9f9430 Distance = 8 -- Final Test ----------------------------------- (sizeof(Struct1) == sizeof(Struct2)) --> true
El compilador decide que las dos estructuras tengan el mismo tamaño, aunque en realidad la segunda desperdicia 3 bytes. Esto lo hace para que los accesos a memoria estén alineados, y lo mejor de todo, en cada arquitectura puede ocurrir una cosa diferente.
Más aún, si medimos dentro de Struct2 dónde empieza el char y dónde empieza el float, tal vez nos llevemos una sorpresa. PodrÃamos pensar que «c» ocupará el primer byte, y que acontinuación vienen 4 bytes que forman el float y que los otros 3 bytes que el compilador mete de relleno, lo hace por el padding.
Si ejecutamos este pequeño código, podremos medir a qué distancia está cada elemento desde el principio del struct:
printf("Distance of c %u\n", (unsigned int)(&test2[0].c) - (unsigned int)(test2)); printf("Distance of f %u\n", (unsigned int)(&test2[0].f) - (unsigned int)(test2));
El resultado es que efectivamente c empieza en el byte 0, pero f lo hace en el 4. Es decir, los 3 bytes de padding están entre el campo «c» y el campo «f», y no al final de la estructura a modo de relleno. El compilador, muy listo, introduce huecos deliberadamente dentro del struct para asegurar que el acceso a cada uno de sus campos, es un acceso alineado.
Lo se, lo se, esto en el fondo es una gran-chorrada™, no importa tener bytes fantasmas por ahÃ, ni nos preocupa por lo general. Pero basta que algún dÃa te declares un struct con tus campos (que seguramente no serán ni dos floats ni un char) que no tengas en cuenta el padding y hagas un cast burro, o un memcpy en plan hack-eficiente. En ese momento las cosas pueden empezar a torcerse 😉
Interesante post 😉
Todos sabemos que el alineamiento es tu amigo, y que los accesos no alineados son lentos y pueden causar náuseas y malestar general, PERO a veces interesa que los campos de una struct en memoria sean exactamente como los hemos declarado (pongamos por caso querer leer una cabecera de tamaño fijo de un binario directamente a una struct). Si queremos hacer esto con gcc basta con declarar:
struct __attribute__((packed)) mi_struct { char c; float f; }
Tambien se puede aplicar este atributo a miembros individuales de la struct. Más info en http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/ secciones «Specifying Attributes of Variables» y «Specifying Attributes of Types».
Para los raros que no usan gcc, un #pragma pack deberia compactar las structs, si no recuerdo mal. Por cierto, molaria tener un boton de vista previa en el cacharro este de poner comentarios 😉
(Dios, que gente más lista lee el blog) Gracias Slack !!! me mola lo del packed 🙂
Lo mejor de un blog son estas pequeñas joyas con la que nos regalan en forma de comentarios. Este comentario me lo apunto, asà como el de stow Por el momento ganáis en el ranking de mejores comentarios XDDD.
PD: Apuntado botón para preview.
Me he topado con el mismo problema al guardar una estructura en un archivo. Al intentar leer la información con otra herramienta (java), me encontré con información que nunca habÃa declarado en el arreglo. Obviamente, no significaban nada para mi.
Muy buena explicación. Intenté utilizar #pragma pack pero no era soportado por mi compilador. Tampoco struct __attribute__((packed)).
Simplemente le dà un acomodo diferente a mis valores, tratando de alinear los bytes a mano, y resultó como debÃa acomodarse.
Suerte!