Gestión de memoria (III)

Lecturas de la serie
Sobre este artículo
Tiempo estimado de lectura: 5 minutos
Nivel: Básico
Última actualización: 10 de mayo, 2026
1. Alineación de memoria
El proceso de alineación de datos en memoria implica el alojamiento de variables de un tipo determinado en direcciones de memoria que sean múltiplos enteros de un cierto valor L (normalmente 1, 2, 4, 8, ó 16) [1]. Concretamente, sea D la dirección en memoria de una variable cualquiera. La alineación L de dicha variable (medida en bytes) se define como la mayor potencia de 2 tal que
D (mod L) = 0.
Con carácter general, el compilador GCC impone la siguiente tabla de alineaciones para tipos fundamentales y punteros en un sistema Linux de 64 bits (siguiendo el modelo de datos LP64):
Tipo Tamaño (bytes) Alineación (bytes)
char 1 1
short 2 2
int 4 4
long int 8 8
long long int 8 8
float 4 4
double 8 8
long double 16* 16
Puntero a cualquier tipo 8 8
* En el caso de long double, sólo 10 bytes son realmente utilizados para la representación numérica (80 bits de precisión); los 6 bytes restantes son añadidos para garantizar una alineación que sea múltiplo entero del tamaño de una palabra [2]. 
La alineación de datos simplifica sustancialmente la comunicación entre la CPU y la memoria del computador. Si un dato no estuviera alineado, la CPU podría verse obligada a realizar dos accesos a memoria para leer un único valor (al quedar éste repartido entre dos palabras de memoria o líneas de caché), reduciendo drásticamente el rendimiento. Así, por ejemplo, las variables de tipo fundamental double, de 8 bytes de tamaño, poseen una alineación L = 8, de modo que sus operaciones de lectura/escritura sean atómicas y eficientes en un solo ciclo de bus de datos.
2. Padding
Dada una estructura o clase, el compilador puede introducir bloques de bytes inutilizados, denominados padding, entre los datos miembros con el fin de garantizar la correcta alineación de todos ellos. Así, por ejemplo, consideremos el agregado:

   struct S {       char a;       int i;       char b;    };

De entrada, la alineación de un objeto cualquiera de tipo S coincidirá con la del dato miembro de mayor alineación (en nuestro caso, el entero int con L = 4). Así, la dirección en memoria de un objeto de tipo S será siempre múltiplo entero de 4. El compilador optará, asimismo, por insertar 3 bytes entre el primer carácter a y el entero i, con el fin de garantizar una alineación igual a 4 para el entero. Finalmente, insertará un bloque de 3 bytes tras el segundo carácter b de manera que, de crearse un array de elementos de tipo S, cada elemento del mismo satisfaga los requerimientos de alineación mencionados anteriormente:

   struct S { // tras compilación       char a;       char __padding_1[3];       int i;       char b;       char __padding_2[3];    };


El tamaño de un objeto de tipo S es, pues, de 12 bytes. Por supuesto, una mera reordenación de los datos miembro de la estructura conseguiría un uso más eficiente de la memoria. Como regla general de optimización, una reordenación de los datos miembro de mayor a menor tamaño conseguirá un uso más eficiente de la memoria. Así, los objetos de la siguiente estructura ocuparían únicamente 8 bytes:

   struct S {       int i; // 4 bytes       char a, b; // 1 + 1 = 2 bytes // + 2 bytes de padding para completar el módulo de 4    }; // tamaño total tras compilación: 8 bytes

3. sizeof, alignof y alignas
En C++, disponemos de herramientas específicas para analizar y gestionar estas restricciones:
  • sizeof(): operador que devuelve el tamaño total (incluyendo el padding) de un tipo cualquiera (fundamental o definido por el programador).
  • alignof(): operador que informa sobre el requerimiento de alineación del tipo.
  • alignas(): especificador que permite modificar la alineación natural de una variable o un dato miembro de una clase, siempre que no se debilite la alineación original de ésta.
A modo de ejemplo, y para la última definición del agregadoS:

   std::println("· size of S: {} bytes\n· alignment of S: {} bytes"sizeof(S), alignof(S));    /* imprime:          · size of S: 8 bytes          · alignment of S: 4 bytes    */

El uso de alignas es crítico al trabajar con búferes de bajo nivel o programación SIMD. En el ejemplo siguiente, se introduce un búfer con una longitud dada de bytes con la máxima alineación posible (al menos tan estricta como la de cualquier tipo escalar, típicamente la de long double, es decir, 8 ó 16 bytes según la plataforma en que se trabaje):

   constexpr auto buffer_size = std::size_t{/* longitud del búfer en bytes */};    alignas(std::max_align_t) unsigned char buffer[buffer_size];
Referencias bibliográficas
  1. Data alignment (definition) – https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=vs-2019
  2. Size of long integer – https://software.intel.com/en-us/articles/size-of-long-integer-type-on-different-architecture-and-os