2012-01-06 5 views
38

seguente estratto dal herecome fa Malloc a capire l'allineamento?

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

alloca storage raw. Infatti, la chiamata malloc alloca memoria che è abbastanza grande e opportunamente allineati per contenere un oggetto di tipo widget di

vedi anche fast pImpl da erba Sutter, ha detto:

allineamento. Qualsiasi allineamento di memoria. Ogni memoria che è allocata dinamicamente via nuovo o malloc è garantito per essere correttamente allineato per oggetti di qualsiasi tipo, ma i buffer che non sono allocati dinamicamente non hanno tale garanzia

io sono curioso di questo, come Malloc conosce l'allineamento del tipo personalizzato?

+2

nuovo e malloc, per default, allineare indirizzo a 8 byte (x86) o 16 byte (x64), che è ottimale per la maggior parte dei dati complessi. Inoltre è il compito sizeof() di ottenere la struct size corretta ** con ** padding interno per l'allineamento, se necessario. –

risposta

38

I requisiti di allineamento sono ricorsivi: l'allineamento di qualsiasi struct è semplicemente l'allineamento più grande di uno qualsiasi dei suoi membri e ciò è inteso in modo ricorsivo. Per esempio, e assumendo che l'allineamento di ciascun tipo fondamentale sia uguale alla sua dimensione (questo non è sempre vero in generale), lo struct X { int; char; double; } ha l'allineamento di double e sarà riempito per essere un multiplo della dimensione del doppio (ad esempio 4 (int), 1 (char), 3 (padding), 8 (double)). Lo struct Y { int; X; float; } ha l'allineamento di X, che è il più grande e uguale all'allineamento di double e Y è disposto di conseguenza: 4 (int), 4 (padding), 16 (X), 4 (float), 4 (padding)).

(Tutti i numeri sono solo esempi e potrebbero differire sulla vostra macchina.)

Pertanto, rompendo verso il basso per i tipi fondamentali, abbiamo solo bisogno di conoscere una manciata di allineamenti fondamentali, e tra coloro c'è un ben noto più grande. C++ definisce anche un tipo maxalign_t (penso) il cui allineamento è quell'allineamento più grande.

Tutto malloc() ha bisogno di fare è di scegliere un indirizzo che è un multiplo di tale valore.

+1

La cosa fondamentale da sottolineare è che questo non include le direttive personalizzate 'align' nel compilatore che potrebbero sovra-allineare i dati. – Mehrdad

+2

Anche se si utilizzano questi dati, si è già al di fuori dell'ambito dello standard, si noti che la memoria allocata in questo modo probabilmente * non soddisferà i requisiti di allineamento per tipi predefiniti come _m256 che sono disponibili come estensioni su alcune piattaforme. – jcoder

+0

Cosa succede quando si specifica un allineamento personalizzato tramite 'alignas' che è maggiore dell'allineamento più grande di un tipo di dati primitivo? – Curious

4

L'unica informazione che può essere utilizzata da malloc() è la dimensione della richiesta passata. In generale, potrebbe fare qualcosa come arrotondare la dimensione passata alla potenza maggiore (o uguale) di due, e allineare la memoria in base a quel valore. Potrebbe anche esserci un limite superiore sul valore di allineamento, ad esempio 8 byte.

Quanto sopra è una discussione ipotetica e l'implementazione effettiva dipende dall'architettura della macchina e dalla libreria di runtime che si sta utilizzando. Forse il tuo malloc() restituisce sempre blocchi allineati su 8 byte e non deve mai fare nulla di diverso.

+0

In sintesi, quindi, 'malloc' utilizza l'allineamento 'worst case' perché non lo sa meglio. Ciò significa che 'calloc' può essere più intelligente perché richiede due argomenti, il numero di oggetti e la dimensione di un singolo oggetto? –

+0

Forse. Forse no.Dovresti dare un'occhiata alla tua libreria di runtime per scoprirlo. –

+1

-1, mi spiace. La tua risposta include la verità, ma include anche la disinformazione. Non è una cosa "forse, forse non"; è specificamente documentato per funzionare in un modo che non dipende dalla dimensione. (Non so * perché * non, però. Sembra che sarebbe perfettamente logico farlo.) – ruakh

14

Penso che la parte più rilevante della citazione Herb Sutter è la parte che ho segnato in grassetto:

allineamento. Qualsiasi allineamento di memoria. Ogni memoria che è allocata dinamicamente tramite nuovo o malloc è garantito per essere correttamente allineato per gli oggetti di qualsiasi tipo, ma i buffer che non sono allocati dinamicamente non hanno tale garanzia

Non deve sapere ciò che si digita avere in mente, perché è l'allineamento per qualsiasi tipo. Su un dato sistema, c'è una dimensione di allineamento massima che è sempre necessaria o significativa; ad esempio, un sistema con parole di quattro byte avrà probabilmente un allineamento massimo di quattro byte.

Questo è anche chiaro dal the malloc(3) man-page, che dice in parte:

I malloc() e calloc() funzioni restituiscono un puntatore alla memoria allocata che è opportunamente allineato per qualsiasi tipo di variabile.

+2

qual è il significato di ogni tipo di variabile? non risponde alla mia domanda. vuol dire che malloc userà sempre la massima dimensione di allineamento in un dato sistema, giusto? – Chang

+2

@Chang: in effetti, si. Inoltre, la citazione è sbagliata. 'new' è garantito per avere" qualsiasi "allineamento quando si assegna' char' o 'unsigned char'. Per altri, potrebbe avere un allineamento più piccolo. –

+0

@Chang: a destra, la dimensione massima di allineamento. "Opportunamente allineato per ogni tipo di variabile" significa "opportunamente allineato per un' int' * e * opportunamente allineato per un puntatore * e * opportunamente allineati per qualsiasi 'struct' * e *...". – ruakh

3

1) Allineare al minimo comune multiplo di tutti gli allineamenti. per esempio. se gli interi richiedono un allineamento di 4 byte, ma i puntatori richiedono 8, quindi allocare tutto a 8 byte di allineamento. Questo fa sì che tutto sia allineato.

2) Utilizzare l'argomento dimensione per determinare l'allineamento corretto. Per le dimensioni ridotte è possibile dedurre il tipo, ad esempio malloc(1) (supponendo che le dimensioni di altri tipi non siano 1) sia sempre un carattere. C++ new ha il vantaggio di essere sicuro per tipo e quindi può sempre prendere decisioni di allineamento in questo modo.

+0

È possibile espandere l'acronimo LCM? Posso indovinare, ma non dovrei. –

+0

Inoltre, ci sono altri tipi in C++ che possono essere 1 byte. Tuttavia, la tua implicazione è corretta, può ancora allinearsi in base alla dimensione del tipo. –

2

precedente per C++ 11 allineamento è stato trattato abbastanza semplice utilizzando il più grande allineamento dove il valore esatto era sconosciuto e malloc/calloc ancora funziona in questo modo. Ciò significa che l'allocazione di malloc è correttamente allineata per qualsiasi tipo.

allineamento errato può causare il comportamento non definito secondo lo standard ma ho visto compilatori x86 essere generosi e solo sanzionare con prestazioni inferiori.

Si noti che è anche possibile modificare l'allineamento tramite le opzioni di compilazione o direttive. (pacchetto pragma per VisualStudio per esempio).

Ma quando si tratta di nuova collocazione, quindi C++ 11 ci porta nuove parole chiave chiamati alignof e alignas. Ecco un codice che mostra l'effetto se l'allineamento massimo del compilatore è maggiore di 1. Il primo posizionamento nuovo è automaticamente buono ma non il secondo.

#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); 
} 

immagino prestazioni di questo codice può essere migliorata con alcune operazioni bit per bit.

EDIT: sostituito costoso modulo di calcolo con operazioni bit per bit. Sperando ancora che qualcuno trovi qualcosa ancora più veloce.

+1

In realtà non è il compilatore, è l'hardware stesso. Su x86 un accesso alla memoria disallineato costringe semplicemente il processore a recuperare i due lati del limite di memoria e a mettere insieme il risultato, quindi è sempre "corretto" se più lento. Ad es. alcuni processori ARM, si otterrebbe un errore del bus e un arresto anomalo del programma.Questo è un po 'un problema perché molti programmatori non sono mai esposti a nient'altro che x86 e quindi potrebbe non sapere che il comportamento è in realtà indefinito invece di limitarsi semplicemente alle prestazioni. – Thomas

+0

Sei corretto, è il software hardware o cpu-microcode ma non il compilatore vero e proprio che ti salva sull'architettura x86. Mi chiedo davvero perché non ci sia api più conveniente per gestire questo. Come se i progettisti di C/C++ volessero che gli sviluppatori entrassero nella trappola. Mi ricorda la trappola di std :: numeric_limits :: min(). Qualcuno l'ha capito bene la prima volta? –

+0

Bene, una volta che sai cosa sta succedendo, non è troppo difficile cambiare il tuo stile di programmazione da tutti i tipi di punizioni di tipo pazzesco a codice ben scritto, per fortuna. Il sistema di tipo C rende abbastanza semplice conservare l'allineamento del tipo, a patto che non si stiano facendo cose pazze di manipolazione dei bit senza prestare attenzione. Ora il codice free-pointer-aliasing d'altra parte ha una semantica molto più rigida ... – Thomas