2012-01-06 11 views
38

siguientes Extraído de here¿Cómo entiende malloc la alineación?

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

asigna almacenamiento prima. De hecho, la llamada malloc asigna almacenamiento que es lo suficientemente grande y convenientemente alineada para sostener un objeto de tipo widget de

también ver fast pImpl de Sutter hierba, dijo:

alineación. Cualquier alineación de memoria. Cualquier memoria que se asigna dinámicamente vía nueva o malloc se garantiza que sea alineado correctamente para objetos de cualquier tipo, pero tampones que no están asignadas dinámicamente no tienen esa garantía

tengo curiosidad por esto, ¿cómo ¿Malloc conoce la alineación del tipo personalizado?

+2

new y malloc, de forma predeterminada, alinean la dirección a 8 bytes (x86) o 16 bytes (x64), que es la óptima para la mayoría de los datos complejos. También es un deber de sizeof() obtener la estructura de tamaño correcto ** con ** relleno interno para la alineación, si es necesario. –

Respuesta

38

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), el struct X { int; char; double; } tiene la alineación de double, y se rellenará para ser un múltiplo del doble (por ej. 4 (int), 1 (char), 3 (relleno), 8 (doble)). El 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, y Y se presenta en consecuencia: 4 (int), 4 (relleno), 16 (X), 4 (flotante), 4 (relleno)

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

Por lo tanto, por lo descomponen a los tipos fundamentales, que sólo necesita saber un puñado de alineaciones fundamentales, y entre los que hay una conocido más grande. C++ incluso define un tipo maxalign_t (creo) cuya alineación es que la alineación más grande.

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

+1

La clave para señalar es que esto no incluye las directivas 'align' personalizadas para el compilador que podría sobre alinear los datos. – Mehrdad

+2

Aunque si los usa, ya está fuera del alcance de la norma, tenga en cuenta que la memoria asignada de esta manera probablemente * no * cumplirá con los requisitos de alineación para tipos compilados como _m256 que están disponibles como extensiones en algunas plataformas. – jcoder

+0

¿Qué sucede cuando especifica una alineación personalizada mediante 'alignas' que es más grande que la alineación más grande de un tipo de datos primitivo? – Curious

4

La única información que malloc() puede usar es el tamaño de la solicitud que se le ha enviado. 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. También es probable que 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 devuelve bloques alineados en 8 bytes y nunca tiene que hacer nada diferente.

+0

En resumen, 'malloc' usa la alineación 'peor caso' porque no conoce nada mejor. ¿Eso significa que 'calloc' puede ser más inteligente porque toma dos args, el número de objetos y el tamaño de un solo objeto? –

+0

Quizás. Tal vez no.Tendría que buscar en la fuente de su biblioteca de tiempo de ejecución para averiguarlo. –

+1

-1, lo siento. Su respuesta incluye la verdad, pero también incluye la desinformación. No es una cosa de "tal vez, tal vez no"; está específicamente documentado para funcionar de una manera que no depende del tamaño. (No sé por qué * no, sin embargo. Parece que tendría mucho sentido que lo haga). – ruakh

14

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

alineación. Cualquier alineación de memoria. Cualquier memoria que se asigna de forma dinámica a través de nuevo o malloc se garantiza que sea alineado correctamente para objetos de cualquier tipo, pero tampones que no están asignadas dinámicamente no tienen esa garantía

No tiene que saber lo que escriba tenga en cuenta, porque se está alineando 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á un máximo de alineación de cuatro bytes.

Esto también queda claro por the malloc(3) man-page, que dice en parte:

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

+2

¿Qué significa cualquier tipo de variable? no responde mi pregunta ¿significa que malloc siempre usará el tamaño máximo de alineación en cualquier sistema dado, ¿verdad? – Chang

+2

@Chang: efectivamente, sí. También tenga en cuenta que la cita es incorrecta. 'new' solo está garantizado para tener" cualquier "alineación cuando se asigna' char' o 'unsigned char'. Para otros, puede tener una alineación más pequeña. –

+0

@Chang: a la derecha, el tamaño de alineación máximo. "Adecuadamente alineado para cualquier tipo de variable" significa "alineado adecuadamente para un' int' * y * alineado adecuadamente para un puntero * y * alineado adecuadamente para cualquier 'struct' * y *...". – ruakh

3

1) Alinee al mínimo común múltiplo de todas las alineaciones. p.ej. si las entradas 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) Utilice 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 otros tipos de tamaños no son 1) es siempre un carácter. C++ new tiene el beneficio de ser tipo seguro y por lo tanto siempre puede tomar decisiones de alineación de esta manera.

+0

¿Se puede ampliar el acrónimo LCM? Puedo adivinar, pero no debería hacerlo. –

+0

Además, hay otros tipos en C++ que pueden ser de 1 byte. Sin embargo, su implicación es correcta, aún puede alinearse según el tamaño del tipo. –

2

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

La alineación incorrecta puede dar como resultado un comportamiento indefinido de acuerdo con la norma, pero he visto que los compiladores x86 son generosos y solo castigan con un rendimiento inferior.

Tenga en cuenta que también puede modificar la alineación a través de las opciones o directivas del compilador. (pragma pack para VisualStudio, por ejemplo).

Pero cuando se trata de la colocación nueva, entonces C++ 11 nos trae nuevas palabras clave denominadas 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 bit a bit.

EDITAR: Reemplazó costoso cálculo de módulo con operaciones bit a bit. Todavía espero que alguien encuentre algo aún más rápido.

+1

No es realmente el compilador, es el hardware en sí. En x86, un acceso a la memoria mal alineado simplemente fuerza al procesador a buscar los dos lados del límite de la memoria y unir el resultado, por lo que siempre es "correcto" si es más lento. En e.g. algunos procesadores ARM, obtendría un error de bus y un bloqueo de programa.Esto es un problema porque muchos programadores nunca están expuestos a algo más que x86, y por lo tanto, es posible que no sepan que el comportamiento en realidad no está definido en lugar de simplemente disminuir el rendimiento. – Thomas

+0

Estás en lo correcto, es el hardware o software de la CPU-microcódigo pero no el compilador real que le ahorra en la arquitectura x86. Realmente me pregunto por qué no hay una API más conveniente para manejar esto. Como si los diseñadores de C/C++ quisieran que los desarrolladores cayeran en la trampa. Me recuerda a std :: numeric_limits :: min() trap. ¿Alguien lo entendió bien la primera vez? –

+0

Bueno, una vez que sabes lo que está pasando, no es muy difícil cambiar tu estilo de programación de todo tipo de juegos de palabras alocados a códigos bien escritos, afortunadamente. El sistema de tipo C hace que sea bastante fácil preservar la alineación de tipos, siempre y cuando no vaya a hacer cosas de manipulación de bits insana sin prestar atención. Ahora, el código libre de alias del puntero por otro lado tiene una semántica mucho más dura ... – Thomas