Alineación de la memoria: ¿cómo usar alignof / alignas?

81

Trabajo con memoria compartida en este momento.

No puedo entender alignofy alignas.

cppreference no está claro: alignofdevuelve "alineación" pero ¿qué es "alineación"? número de bytes que se deben agregar para alinear el siguiente bloque? tamaño acolchado? Las entradas de stack overflow / blogs tampoco están claras.

¿Alguien puede explicarlo claramente alignofy alignas?

Offirmo
fuente
1
cppreference está tratando de ser una referencia en lugar de un tutorial
Cubbi
1
@Cubbi: también puede consultar en cplusplus.com, hay un debate sobre qué sitio es mejor, para ciertos temas cplusplus es mejor, para otros cppreference es mejor, encontré que ambos sitios en ciertos momentos no son buenos
CoffeDeveloper
2
@DarioOO Solo estaba respondiendo por qué cppreference no explica el concepto de alineación en la alignofpágina (lo hace ahora, en la página del objeto de trabajo en progreso ). No veo la relevancia de cplusplus.com.
Cubbi

Respuestas:

81

La alineación es una restricción sobre qué posiciones de memoria se pueden almacenar el primer byte de un valor. (Es necesario para mejorar el rendimiento de los procesadores y permitir el uso de ciertas instrucciones que funcionan solo en datos con una alineación particular, por ejemplo, SSE debe alinearse a 16 bytes, mientras que AVX a 32 bytes).

La alineación de 16 significa que las direcciones de memoria que son múltiplos de 16 son las únicas direcciones válidas.

alignas

forzar la alineación al número requerido de bytes. Solo puedes alinear a potencias de 2: 1, 2, 4, 8, 16, 32, 64, 128, ...

#include <cstdlib>
#include <iostream>

int main() {
    alignas(16) int a[4];
    alignas(1024) int b[4];
    printf("%p\n", a);
    printf("%p", b);
}

ejemplo de salida:

0xbfa493e0
0xbfa49000  // note how many more "zeros" now.
// binary equivalent
1011 1111 1010 0100 1001 0011 1110 0000
1011 1111 1010 0100 1001 0000 0000 0000 // every zero is just a extra power of 2

la otra palabra clave

alignof

es muy conveniente, no puedes hacer algo como

int a[4];
assert(a % 16 == 0); // check if alignment is to 16 bytes: WRONG compiler error

pero tu puedes hacer

assert(alignof(a) == 16);
assert(alignof(b) == 1024);

tenga en cuenta que en realidad esto es más estricto que una simple operación de "%" (módulo). De hecho, sabemos que algo alineado a 1024 bytes está necesariamente alineado a 1, 2, 4, 8 bytes pero

 assert(alignof(b) == 32); // fail.

Entonces, para ser más precisos, "alignof" devuelve la mayor potencia de 2 con la que algo está alineado.

También alignof es una buena manera de conocer de antemano el requisito de alineación mínimo para tipos de datos básicos (probablemente devolverá 1 para caracteres, 4 para flotante, etc.).

Todavía legal:

alignas(alignof(float)) float SqDistance;

Algo con una alineación de 16 se colocará en la siguiente dirección disponible que sea un múltiplo de 16 (puede haber un relleno implícito de la última dirección utilizada).

Café Desarrollador
fuente
10
A diferencia sizeof, alignofsolo se puede aplicar a un type-id.
neverhoodboy
se evalúa alignof()(y la contraparte alignas()) en tiempo de compilación, por lo que no hay gastos generales de tiempo de ejecución?
sinsentido
No. No es posible, el compilador puede hacer eso como optimización en muy pocos casos, pero en general no sabrá cómo se alinean las direcciones de memoria antes de evaluar las 2 funciones. Solo mire el ensamblado generado por mi ejemplo: goo.gl/ZbemBF
CoffeDeveloper
1
@Serthy Para aclarar alignof es una constante en tiempo de compilación. alignasno lo es, y tendrá que ser respaldado por su implementación de new(requisito del estándar), o por un asignador estándar personalizado .
Aidiakapi
Buena respuesta, pero necesita un tratamiento structy miembros de la estructura que son static. alignasestá resultando ser mucho más delicado que __attribute__((aligned)), especialmente en compiladores como Clang.
jww
11

La alineación no es relleno (aunque a veces se introduce relleno para satisfacer los requisitos de alineación). Es una propiedad intrínseca de tipo C ++. Para ponerlo en standardese ( 3.11[basic.align])

Los tipos de objetos tienen requisitos de alineación (3.9.1, 3.9.2) que imponen restricciones sobre las direcciones en las que puede asignarse un objeto de ese tipo. Una alineación es un valor entero definido por la implementación que representa el número de bytes entre direcciones sucesivas en las que se puede asignar un objeto dado. Un tipo de objeto impone un requisito de alineación a cada objeto de ese tipo; Se puede solicitar una alineación más estricta utilizando el especificador de alineación (7.6.2).

Cubbi
fuente
1
Muy interesante. ¿Le importaría dar algunos ejemplos? ¿Tiene alignof (estructura X) == tamaño de (estructura X)? Por qué no ?
Offirmo
1
@Offirmo no, excepto por concordia: struct X { char a; char b}tiene tamaño 2 y requisito de alineación 1, en sistemas cuerdos (se puede asignar en cualquier dirección porque se puede asignar un carácter en cualquier dirección)
Cubbi
requisito de alineación de 1 ???? Oh, lo entiendo: pensé que la alineación siempre estaba en límites "naturales" de 32 bits / 64 bits, pero aparentemente no. Eso explica las cosas ... Entonces, en las máquinas habituales, el resultado de alignof () siempre tendrá un máximo de 4 (32 bits) u 8 (64 bits) ¿Estoy en lo cierto?
Offirmo
@Offirmo "natural" alignof alcanzará el máximo en alignof(std::max_align_t), que está 16en mi linux (independientemente de si se compila -m32 o -m64), pero puede hacerlo más estricto conalignas
Cubbi
7

Cada tipo tiene un requisito de alineación. Generalmente, esto es para que se pueda acceder de manera eficiente a las variables del tipo, sin tener que hacer que la CPU genere más de un acceso de lectura / escritura para llegar a cualquier miembro dado del tipo de datos. Además, también asegura una copia eficiente de toda la variable. alignofdevolverá el requisito de alineación para el tipo dado.

alignasse usa para forzar una alineación en un tipo de datos (siempre que no sea menos estricto que lo alignofque devolvería dicho tipo de datos)

Levengli
fuente
3

La alineación es una propiedad relacionada con la dirección de memoria. Simplemente podemos decir que si una dirección X está alineada con Z, entonces x es un múltiplo de Z, es decir, X = Zn + 0. Aquí lo importante es que Z siempre es una potencia de 2.

La alineación es una propiedad de una dirección de memoria, expresada como la dirección numérica módulo una potencia de 2. Por ejemplo, la dirección 0x0001103F módulo 4 es 3. Se dice que esa dirección está alineada con 4n + 3, donde 4 indica la potencia elegida 2. La alineación de una dirección depende de la potencia elegida de 2. La misma dirección módulo 8 es 7. Se dice que una dirección está alineada con X si su alineación es Xn + 0.

La declaración anterior se encuentra en la referencia de Microsoft C ++.

Si un elemento de datos se almacena en la memoria con una dirección que está alineada con su tamaño, entonces se dice que ese elemento de datos está alineado naturalmente , de lo contrario, desalineado. Por ejemplo: si una variable entera con un tamaño de 4 bytes se almacena en una dirección que está alineada con 4, entonces podemos decir que la variable está naturalmente alineada, es decir, la dirección de la variable debe ser un múltiplo de 4.

Los compiladores siempre intentan evitar desalineamientos. Para tipos de datos simples, las direcciones se eligen de manera que sea un múltiplo del tamaño de la variable en bytes. El cumplidor también se rellena de forma adecuada en el caso de estructuras para una alineación y acceso naturales, aquí la estructura se alineará al máximo de los tamaños de los diferentes elementos de datos en la estructura, por ejemplo:

    struct abc
   {
        int a;
        char b;
   };

Aquí, la estructura abc se alinea con 4, que es el tamaño del miembro int que obviamente es mayor que 1 byte (tamaño del miembro char).

alinear

Este especificador se usa para alinear tipos definidos por el usuario como estructura, clase, etc. a un valor particular que es una potencia de 2.

alinear

Este es un tipo de operador para obtener el valor con el que se alinea la estructura o el tipo de clase. p.ej:

#include <iostream>
struct alignas(16) Bar
{
    int i; // 4 bytes
    int n; // 4 bytes
    short s; // 2 bytes
};
int main()
{
    std::cout << alignof(Bar) << std::endl; // output: 16
}
JOSÉ BENOY
fuente