Alineación
Una de las características de bajo nivel de C++ es la capacidad para especificar la alineación precisa de los objetos en la memoria para sacar el máximo partido de una arquitectura de hardware específica. De forma predeterminada, el compilador alinea los miembros de clase y estructura en su valor de tamaño: bool
y char
en límites de 1 byte, short
en límites de 2 bytes, int
, long
y float
en límites de 4 bytes y long long
, double
y long double
en límites de 8 bytes.
En la mayoría de los escenarios no tendrá que preocuparse jamás por la alineación, puesto que la alineación predeterminada ya es óptima. No obstante, en algunos casos se pueden conseguir importantes mejoras de rendimiento o ahorros de memoria al especificar una alineación personalizada para sus estructuras de datos. Antes de Visual Studio 2015 podía usar las palabras clave __alignof
y __declspec(align)
específicas de Microsoft para especificar una alineación mayor que el valor predeterminado. Desde Visual Studio 2015 se deben usar las palabras clave estándar de C++11 alignof
y alignas
para aumentar al máximo la portabilidad de su código. Las nuevas palabras clave se comportan de la misma manera que las extensiones específicas de Microsoft. La documentación de esas extensiones también se aplica a las nuevas palabras clave. Para obtener más información, vea alignof
Operador, alignas
Especificador y alineación. El estándar de C++ no especifica el comportamiento de empaquetado para alinear en límites inferiores al valor predeterminado del compilador para la plataforma de destino, por lo que seguirá teniendo que usar el #pragma pack
de Microsoft en ese caso.
Use la clase aligned_storage para la asignación de memoria de estructuras de datos con alineaciones personalizadas. La clase aligned_union consiste en especificar la alineación de uniones con constructores o destructores notriviales.
Alineación y direcciones de memoria
La alineación es una propiedad de una dirección de memoria, expresada como el módulo de la dirección numérica a una potencia de 2. Por ejemplo, la dirección 0x0001103F módulo 4 es 3. Esa dirección está alineada con 4n+3, donde 4 indica la potencia de 2 elegida. La alineación de una dirección depende de la potencia de 2 elegida. El mismo módulo de dirección 8 es 7. Se dice que una dirección está alineada con X si su alineación es Xn+0.
Las CPU ejecutan instrucciones que funcionan con datos almacenados en memoria. Los datos se identifican mediante sus direcciones en memoria. Un solo dato también tiene un tamaño. Un dato está alineado naturalmente si su dirección está alineada a su tamaño. De lo contrario, se llama desalineado. Por ejemplo, un dato de punto flotante de 8 bytes se encuentra alineado naturalmente si la dirección que se utiliza para identificarlo está alineada a 8.
Control del compilador de la alineación de datos
Los compiladores intentan realizar asignaciones de datos de forma que impidan la desalineación de datos.
Para los tipos de datos simples, el compilador asigna direcciones que son múltiplos del tamaño en bytes del tipo de datos. Por ejemplo, el compilador asigna direcciones a variables de tipo long
que son múltiplos de 4, estableciendo los 2 bits inferiores de la dirección en cero.
El compilador también incluye estructuras de una manera que alinea de forma natural cada elemento de la estructura. Considere la estructura struct x_
en el siguiente ejemplo de código:
struct x_
{
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
} bar[3];
El compilador rellena esta estructura para aplicar la alineación de forma natural.
En el siguiente ejemplo de código se muestra cómo el compilador coloca la estructura rellenada en memoria:
// Shows the actual memory layout
struct x_
{
char a; // 1 byte
char _pad0[3]; // padding to put 'b' on 4-byte boundary
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
char _pad1[1]; // padding to make sizeof(x_) multiple of 4
} bar[3];
Ambas declaraciones devuelven sizeof(struct x_)
como 12 bytes.
La segunda declaración incluye dos elementos de relleno:
char _pad0[3]
para alinear el miembroint b
en un límite de 4 bytes.char _pad1[1]
para alinear los elementos de matriz de la estructurastruct _x bar[3];
en un límite de 4 bytes.
El relleno alinea los elementos de bar[3]
de tal forma que permite el acceso natural.
En el código de ejemplo siguiente se muestra el diseño de matriz bar[3]
:
adr offset element
------ -------
0x0000 char a; // bar[0]
0x0001 char pad0[3];
0x0004 int b;
0x0008 short c;
0x000a char d;
0x000b char _pad1[1];
0x000c char a; // bar[1]
0x000d char _pad0[3];
0x0010 int b;
0x0014 short c;
0x0016 char d;
0x0017 char _pad1[1];
0x0018 char a; // bar[2]
0x0019 char _pad0[3];
0x001c int b;
0x0020 short c;
0x0022 char d;
0x0023 char _pad1[1];
alignof
y alignas
El alignas
especificador es una manera estándar portátil de C++ para especificar la alineación personalizada de variables y tipos definidos por el usuario. El operador alignof
también es una forma estándar y portátil de obtener la alineación de una variable o un tipo especificado.
Ejemplo
Puede usar alignas
en una clase, struct o union, o en miembros individuales. Cuando se encuentran varios alignas
especificadores, el compilador elige el que tiene el valor más grande.
// alignas_alignof.cpp
// compile with: cl /EHsc alignas_alignof.cpp
#include <iostream>
struct alignas(16) Bar
{
int i; // 4 bytes
int n; // 4 bytes
alignas(4) char arr[3];
short s; // 2 bytes
};
int main()
{
std::cout << alignof(Bar) << std::endl; // output: 16
}