c++ c memory-alignment

c++ - ¿Cómo entiende Malloc la alineación?



memory-alignment (6)

1) Alinear al mínimo común múltiplo de todas las alineaciones. por ejemplo, si los ints requieren una alineación de 4 bytes, pero los punteros requieren 8, entonces asigne todo a la alineación de 8 bytes. Esto hace que todo esté alineado.

2) Usa el argumento de tamaño para determinar la alineación correcta. Para tamaños pequeños, puede inferir el tipo, como malloc(1) (suponiendo que los tamaños de otros tipos no sean 1) siempre es un carácter. C ++ new tiene la ventaja de ser de tipo seguro y, por lo tanto, siempre puede tomar decisiones de alineación de esta manera.

siguiente extracto de here

pw = (widget *)malloc(sizeof(widget));

asigna almacenamiento en bruto. De hecho, la llamada a malloc asigna un almacenamiento lo suficientemente grande y adecuadamente alineado para contener un objeto de tipo widget

También ver rápido pImpl de hierba sutter, dijo:

Alineación . Alineación de cualquier memoria. Cualquier memoria asignada dinámicamente a través de new o malloc está garantizada para estar correctamente alineada para objetos de cualquier tipo, pero los buffers que no están asignados dinámicamente no tienen tal garantía

Tengo curiosidad acerca de esto, ¿cómo sabe malloc la alineación del tipo personalizado?


Antes de C ++ 11, la alineación se trató de manera bastante simple utilizando la alineación más grande donde se desconocía el valor exacto y malloc / calloc todavía funciona de esta manera. Esto significa que la asignación de malloc está correctamente alineada para cualquier tipo.

La alineación incorrecta puede dar como resultado un comportamiento indefinido de acuerdo con el estándar, pero he visto que los compiladores x86 son generosos y solo castigan con un rendimiento más bajo.

Tenga en cuenta que también puede modificar la alineación mediante las opciones o directivas del compilador. (Pragma pack para VisualStudio por ejemplo).

Pero cuando se trata de una nueva ubicación , entonces C ++ 11 nos trae nuevas palabras clave llamadas alignof y alignas. Aquí hay un código que muestra el efecto si la alineación máxima del compilador es mayor que 1. La primera ubicación nueva a continuación es automáticamente buena pero no la segunda.

#include <iostream> #include <malloc.h> using namespace std; int main() { struct A { char c; }; struct B { int i; char c; }; unsigned char * buffer = (unsigned char *)malloc(1000000); long mp = (long)buffer; // First placment new long alignofA = alignof(A) - 1; cout << "alignment of A: " << std::hex << (alignofA + 1) << endl; cout << "placement address before alignment: " << std::hex << mp << endl; if (mp&alignofA) { mp |= alignofA; ++mp; } cout << "placement address after alignment : " << std::hex <<mp << endl; A * a = new((unsigned char *)mp)A; mp += sizeof(A); // Second placment new long alignofB = alignof(B) - 1; cout << "alignment of B: " << std::hex << (alignofB + 1) << endl; cout << "placement address before alignment: " << std::hex << mp << endl; if (mp&alignofB) { mp |= alignofB; ++mp; } cout << "placement address after alignment : " << std::hex << mp << endl; B * b = new((unsigned char *)mp)B; mp += sizeof(B); }

Supongo que el rendimiento de este código se puede mejorar con algunas operaciones bitwise.

EDITAR: Se reemplazó el costoso cálculo de módulo con operaciones bitwise. Todavía con la esperanza de que alguien encuentre algo aún más rápido.


Creo que la parte más relevante de la cita de Herb Sutter es la parte que he marcado en negrita:

Alineación. Alineación de cualquier memoria. Cualquier memoria asignada dinámicamente a través de new o malloc está garantizada para estar correctamente alineada para objetos de cualquier tipo , pero los buffers que no están asignados dinámicamente no tienen tal garantía

No tiene que saber qué tipo tiene en mente, porque está alineado para cualquier tipo. En cualquier sistema dado, hay un tamaño máximo de alineación que siempre es necesario o significativo; por ejemplo, un sistema con palabras de cuatro bytes probablemente tendrá una alineación máxima de cuatro bytes.

Esto también queda claro en la página de manual de malloc(3) , que dice en parte:

Las funciones malloc() y calloc() devuelven un puntero a la memoria asignada que está adecuadamente alineada para cualquier tipo de variable .


La única información que puede usar malloc() es el tamaño de la solicitud que se le pasa. En general, podría hacer algo como redondear el tamaño pasado a la potencia mayor (o igual) más cercana de dos y alinear la memoria en función de ese valor. Es probable que también haya un límite superior en el valor de alineación, como 8 bytes.

Lo anterior es una discusión hipotética, y la implementación real depende de la arquitectura de la máquina y la biblioteca de tiempo de ejecución que está utilizando. Tal vez su malloc() siempre devuelva bloques alineados en 8 bytes y nunca tenga que hacer nada diferente.


Los requisitos de alineación son recursivos: la alineación de cualquier struct es simplemente la alineación más grande de cualquiera de sus miembros, y esto se entiende recursivamente.

Por ejemplo, y suponiendo que la alineación de cada tipo fundamental es igual a su tamaño (esto no siempre es cierto en general), la struct X { int; char; double; } struct X { int; char; double; } struct X { int; char; double; } tiene la alineación de double y se rellenará para que sea un múltiplo del tamaño de doble (por ejemplo, 4 (int), 1 (char), 3 (relleno), 8 (doble)). La struct Y { int; X; float; } struct Y { int; X; float; } struct Y { int; X; float; } tiene la alineación de X , que es la más grande e igual a la alineación de double , e Y se presenta en consecuencia: 4 (int), 4 (relleno), 16 (X), 4 (flotador), 4 (relleno) .

(Todos los números son solo ejemplos y pueden diferir en su máquina).

Por lo tanto, al dividirlo en los tipos fundamentales, solo necesitamos conocer un puñado de alineamientos fundamentales, y entre ellos hay uno más grande conocido. C ++ incluso define un tipo maxalign_t (creo) cuya alineación es la mayor alineación.

Todo lo que necesita hacer malloc() es elegir una dirección que sea un múltiplo de ese valor.


malloc no tiene conocimiento de para qué se está asignando porque su parámetro es solo el tamaño total. Simplemente se alinea con una alineación que es segura para cualquier objeto.