Come allocare la memoria allineata solo usando la libreria standard?


422

Ho appena finito un test come parte di un colloquio di lavoro e una domanda mi ha sorpreso, anche usando Google come riferimento. Mi piacerebbe vedere cosa può farci l'equipaggio StackOverflow:

La memset_16alignedfunzione richiede un puntatore allineato a 16 byte passato ad essa, o si bloccherà.

a) Come assegnereste 1024 byte di memoria e allineereste a un limite di 16 byte?
b) Liberare la memoria dopo l' memset_16alignedesecuzione.

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}

89
hmmm ... per la fattibilità del codice a lungo termine, che ne dici di "Fire whoever ha scritto memset_16 allineato e riparalo o sostituiscilo in modo che non abbia una particolare condizione al contorno"
Steven A. Lowe,

29
Certamente una domanda valida da porre: "perché il particolare allineamento della memoria". Ma ci possono essere buone ragioni per questo - in questo caso, potrebbe essere che memset_16aligned () possa usare numeri interi a 128 bit e questo è più facile se si sa che la memoria è allineata. Ecc.
Jonathan Leffler,

5
Chiunque abbia scritto memset può usare l'allineamento interno a 16 byte per cancellare il ciclo interno e un piccolo prologo / epilog di dati per ripulire le estremità non allineate. Sarebbe molto più semplice che fare in modo che i programmatori gestiscano ulteriori puntatori di memoria.
Adisak,

8
Perché qualcuno dovrebbe voler allineare i dati a un limite di 16 byte? Probabilmente per caricarlo nei registri SSE a 128 bit. Credo che i (nuovi) movimenti non allineati (ad esempio, movupd, lddqu) siano più lenti o forse stiano prendendo di mira i processori senza SSE2 / 3

11
L'allineamento dell'indirizzo comporta un utilizzo ottimizzato della cache e una maggiore larghezza di banda tra i diversi livelli di cache e RAM (per i carichi di lavoro più comuni). Vedi qui stackoverflow.com/questions/381244/purpose-of-memory-alignment
Deep Thought

Risposte:


587

Risposta originale

{
    void *mem = malloc(1024+16);
    void *ptr = ((char *)mem+16) & ~ 0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

Risposta fissa

{
    void *mem = malloc(1024+15);
    void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

Spiegazione come richiesto

Il primo passo è quello di allocare abbastanza spazio libero, per ogni evenienza. Poiché la memoria deve essere allineata a 16 byte (il che significa che l'indirizzo di byte iniziale deve essere un multiplo di 16), l'aggiunta di 16 byte aggiuntivi garantisce che abbiamo spazio sufficiente. Da qualche parte nei primi 16 byte, c'è un puntatore allineato a 16 byte. (Si noti che malloc()dovrebbe restituire un puntatore che è sufficientemente ben allineata per qualsiasi . Scopo Tuttavia, il significato di 'qualsiasi' è in primo luogo per le cose come base i tipi - long, double, long double, long long., E puntatori a oggetti e puntatori a funzioni Quando si è facendo cose più specializzate, come giocare con i sistemi grafici, possono aver bisogno di un allineamento più rigoroso rispetto al resto del sistema, quindi domande e risposte come questa.)

Il prossimo passo è convertire il puntatore vuoto in un puntatore carattere; Nonostante GCC, non dovresti eseguire l'aritmetica dei puntatori su puntatori vuoti (e GCC ha opzioni di avviso che ti informano quando lo abusi). Quindi aggiungi 16 al puntatore iniziale. Supponiamo che malloc()ti abbia restituito un puntatore incredibilmente mal allineato: 0x800001. Aggiungendo il 16 si ottiene 0x800011. Ora voglio arrotondare per difetto al limite di 16 byte - quindi voglio reimpostare gli ultimi 4 bit su 0. 0x0F ha gli ultimi 4 bit impostati su uno; pertanto, ~0x0Ftutti i bit sono impostati su uno tranne gli ultimi quattro. Anding che con 0x800011 dà 0x800010. Puoi iterare sugli altri offset e vedere che la stessa aritmetica funziona.

L'ultimo passo free()è semplice: tu ritorni sempre e solo a free()un valore di cui sei tornato malloc(), calloc()o realloc()qualsiasi altra cosa è un disastro. Hai fornito correttamente memper mantenere quel valore - grazie. Il libero lo rilascia.

Infine, se conosci gli interni del mallocpacchetto del tuo sistema , puoi immaginare che potrebbe restituire dati allineati a 16 byte (o potrebbe essere allineato a 8 byte). Se fosse allineato a 16 byte, non sarà necessario annusare i valori. Tuttavia, questo è ingannevole e non portatile - altri mallocpacchetti hanno diversi allineamenti minimi e quindi assumere una cosa quando fa qualcosa di diverso porterebbe a core dump. Entro ampi limiti, questa soluzione è portatile.

Qualcun altro ha menzionato posix_memalign()come un altro modo per ottenere la memoria allineata; che non è disponibile ovunque, ma spesso può essere implementato usando questo come base. Si noti che era conveniente che l'allineamento fosse una potenza di 2; altri allineamenti sono più disordinati.

Ancora un commento: questo codice non verifica che l'allocazione abbia avuto esito positivo.

Emendamento

Il programmatore di Windows ha sottolineato che non è possibile eseguire operazioni di maschera di bit sui puntatori e, in effetti, GCC (testato 3.4.6 e 4.3.1) si lamenta in questo modo. Quindi, segue una versione modificata del codice di base, convertita in un programma principale. Ho anche preso la libertà di aggiungere solo 15 invece di 16, come è stato sottolineato. Sto usando da uintptr_tquando C99 è stato abbastanza a lungo per essere accessibile sulla maggior parte delle piattaforme. Se non fosse per l'uso di PRIXPTRnelle printf()dichiarazioni, sarebbe sufficiente #include <stdint.h>invece di usare #include <inttypes.h>. [Questo codice include la correzione evidenziata da CR , che stava ribadendo un punto sollevato per la prima volta da Bill K alcuni anni fa, che fino ad ora sono riuscito a trascurare.]

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

int main(void)
{
    void *mem = malloc(1024+15);
    void *ptr = (void *)(((uintptr_t)mem+15) & ~ (uintptr_t)0x0F);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
    return(0);
}

Ed ecco una versione leggermente più generalizzata, che funzionerà per dimensioni che hanno una potenza di 2:

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

static void test_mask(size_t align)
{
    uintptr_t mask = ~(uintptr_t)(align - 1);
    void *mem = malloc(1024+align-1);
    void *ptr = (void *)(((uintptr_t)mem+align-1) & mask);
    assert((align & (align - 1)) == 0);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

int main(void)
{
    test_mask(16);
    test_mask(32);
    test_mask(64);
    test_mask(128);
    return(0);
}

Per convertire test_mask()in una funzione di allocazione per scopi generici, il singolo valore di ritorno dall'allocatore dovrebbe codificare l'indirizzo di rilascio, come indicato da diverse persone nelle loro risposte.

Problemi con gli intervistatori

Uri ha commentato: Forse stamattina sto riscontrando un problema di comprensione della lettura, ma se la domanda dell'intervista dice nello specifico: "Come assegneresti 1024 byte di memoria" e tu assegneresti chiaramente altro. Non sarebbe un fallimento automatico dell'intervistatore?

La mia risposta non si adatta a un commento di 300 caratteri ...

Dipende, suppongo. Penso che la maggior parte delle persone (incluso me) abbia posto la domanda nel senso "Come assegneresti uno spazio in cui è possibile memorizzare 1024 byte di dati e dove l'indirizzo di base è un multiplo di 16 byte". Se l'intervistatore intendeva davvero come si possono allocare 1024 byte (solo) e averlo allineato a 16 byte, le opzioni sono più limitate.

  • Chiaramente, una possibilità è di allocare 1024 byte e quindi assegnare a tale indirizzo il "trattamento di allineamento"; il problema con questo approccio è che lo spazio disponibile effettivo non è determinato correttamente (lo spazio utilizzabile è compreso tra 1008 e 1024 byte, ma non era disponibile un meccanismo per specificare quale dimensione), il che lo rende poco utile.
  • Un'altra possibilità è che si prevede di scrivere un allocatore di memoria completo e assicurarsi che il blocco da 1024 byte restituito sia adeguatamente allineato. In tal caso, probabilmente finisci per fare un'operazione abbastanza simile a quella della soluzione proposta, ma la nascondi all'interno dell'allocatore.

Tuttavia, se l'intervistatore si aspettasse una di quelle risposte, mi aspetterei che riconoscessero che questa soluzione risponde a una domanda strettamente correlata, e quindi riformulere la loro domanda per indirizzare la conversazione nella direzione corretta. (Inoltre, se l'intervistatore si fosse davvero mosso, non avrei voluto il lavoro; se la risposta a un requisito insufficientemente preciso venisse abbattuta in fiamme senza correzione, l'intervistatore non sarebbe qualcuno per il quale è sicuro lavorare.)

Il mondo va avanti

Il titolo della domanda è cambiato di recente. È stato Risolvere l'allineamento della memoria nella domanda dell'intervista in C che mi ha lasciato senza parole . Il titolo rivisto ( Come allocare la memoria allineata usando solo la libreria standard? ) Richiede una risposta leggermente rivista - questo addendum lo fornisce.

C11 (ISO / IEC 9899: 2011) funzione aggiunta aligned_alloc():

7.22.3.1 La aligned_allocfunzione

Sinossi

#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

Descrizione
La aligned_allocfunzione alloca spazio per un oggetto il cui allineamento è specificato da alignment, la cui dimensione è specificata da sizee il cui valore è indeterminato. Il valore di alignmentdeve essere un allineamento valido supportato dall'attuazione e il valore di sizedeve essere un multiplo integrale di alignment.

Restituisce
La aligned_allocfunzione restituisce un puntatore nullo o un puntatore allo spazio allocato.

E POSIX definisce posix_memalign():

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

DESCRIZIONE

La posix_memalign()funzione deve allocare sizebyte allineati su un limite specificato da alignment, e deve restituire un puntatore alla memoria allocata in memptr. Il valore di alignmentdeve essere una potenza di due multipli di sizeof(void *).

In caso di completamento con esito positivo, il valore indicato da memptrdeve essere un multiplo di alignment.

Se la dimensione dello spazio richiesto è 0, il comportamento è definito dall'implementazione; il valore restituito memptrdeve essere un puntatore nullo o un puntatore univoco.

La free()funzione deve deallocare la memoria precedentemente allocata da posix_memalign().

VALORE DI RITORNO

In caso di completamento con esito positivo, posix_memalign()deve restituire zero; in caso contrario, verrà restituito un numero di errore per indicare l'errore.

Uno o entrambi questi potrebbero essere utilizzati per rispondere alla domanda ora, ma solo la funzione POSIX era un'opzione quando la domanda era originariamente risposta.

Dietro le quinte, la nuova funzione di memoria allineata fa lo stesso lavoro delineato nella domanda, tranne per il fatto che hanno la capacità di forzare l'allineamento più facilmente e di tenere traccia dell'inizio della memoria allineata internamente in modo che il codice non devono occuparsi in modo particolare: libera solo la memoria restituita dalla funzione di allocazione utilizzata.


13
E sono arrugginito con C ++, ma non mi fido davvero che ~ 0x0F si espanda correttamente alla dimensione del puntatore. In caso contrario, si scatenerà l'inferno perché maschererai anche i bit più significativi del tuo puntatore. Potrei sbagliarmi però.
Bill K,

66
BTW '+15' funziona così come '+16' ... nessun impatto pratico in questa situazione però.
Menkboy,

15
I commenti "+ 15" di Menkboy e Greg sono corretti, ma quasi sicuramente malloc () lo arrotonderebbe a 16. L'uso di +16 è leggermente più facile da spiegare. La soluzione generalizzata è complicata, ma fattibile.
Jonathan Leffler,

6
@Aerovistae: è leggermente una domanda trabocchetto, e dipende principalmente dalla tua comprensione di come fare in modo che un numero arbitrario (in realtà l'indirizzo che viene restituito dall'allocatore di memoria) corrisponda a un determinato requisito (multiplo di 16). Se ti venisse detto di arrotondare 53 al multiplo più vicino di 16, come lo faresti? Il processo non è molto diverso per gli indirizzi; è solo che i numeri con cui hai a che fare sono più grandi. Non dimenticare, le domande del colloquio sono poste per scoprire come pensi, non per sapere se conosci la risposta.
Jonathan Leffler,

3
@akristmann: il codice originale è corretto se hai a disposizione <inttypes.h>da C99 (almeno per la stringa di formato - probabilmente, i valori devono essere passati con un cast :) (uintptr_t)mem, (uintptr_t)ptr. La stringa di formato si basa sulla concatenazione di stringhe e la macro PRIXPTR è l'identificatore di printf()lunghezza e tipo corretti per l'output esadecimale per un uintptr_tvalore. L'alternativa è usare, %pma l'output da questo varia a seconda della piattaforma (alcuni aggiungono un vantaggio 0x, la maggior parte non lo fanno) ed è tipicamente scritto con cifre esadecimali minuscole, che non mi piace; quello che ho scritto è uniforme su tutte le piattaforme.
Jonathan Leffler,

58

Tre risposte leggermente diverse a seconda di come guardi la domanda:

1) Abbastanza buono per la domanda esatta fatta è la soluzione di Jonathan Leffler, tranne che per arrotondare a 16 allineati, sono necessari solo 15 byte extra, non 16.

UN:

/* allocate a buffer with room to add 0-15 bytes to ensure 16-alignment */
void *mem = malloc(1024+15);
ASSERT(mem); // some kind of error-handling code
/* round up to multiple of 16: add 15 and then round down by masking */
void *ptr = ((char*)mem+15) & ~ (size_t)0x0F;

B:

free(mem);

2) Per una funzione di allocazione della memoria più generica, il chiamante non vuole tenere traccia di due puntatori (uno da usare e uno da liberare). Quindi memorizzi un puntatore al buffer "reale" sotto il buffer allineato.

UN:

void *mem = malloc(1024+15+sizeof(void*));
if (!mem) return mem;
void *ptr = ((char*)mem+sizeof(void*)+15) & ~ (size_t)0x0F;
((void**)ptr)[-1] = mem;
return ptr;

B:

if (ptr) free(((void**)ptr)[-1]);

Si noti che diversamente da (1), dove sono stati aggiunti solo 15 byte a mem, questo codice potrebbe effettivamente ridurre l'allineamento se l'implementazione dovesse garantire l'allineamento a 32 byte dal malloc (improbabile, ma in teoria un'implementazione C potrebbe avere un 32 byte tipo allineato). Non importa se tutto ciò che fai è chiamare memset_16allineato, ma se usi la memoria per una struttura, allora potrebbe importare.

Non sono sicuro che sia una buona soluzione per questo (oltre a avvisare l'utente che il buffer restituito non è necessariamente adatto a strutture arbitrarie) poiché non c'è modo di determinare programmaticamente quale sia la garanzia di allineamento specifica dell'implementazione. Immagino che all'avvio potresti allocare due o più buffer da 1 byte e supporre che il peggior allineamento che vedi sia quello garantito. Se sbagli, sprechi memoria. Chiunque abbia un'idea migliore, per favore, dillo ...

[ Aggiunto : il trucco "standard" è quello di creare un'unione di "tipi probabilmente allineati al massimo" per determinare l'allineamento richiesto. È probabile che i tipi allineati al massimo siano (in C99) ' long long', ' long double', ' void *' o ' void (*)(void)'; se includi <stdint.h>, potresti presumibilmente usare ' intmax_t' al posto di long long(e, su macchine Power 6 (AIX), intmax_tti darebbe un tipo intero a 128 bit). I requisiti di allineamento per quell'unione possono essere determinati incorporandolo in una struttura con un singolo carattere seguito dall'unione:

struct alignment
{
    char     c;
    union
    {
        intmax_t      imax;
        long double   ldbl;
        void         *vptr;
        void        (*fptr)(void);
    }        u;
} align_data;
size_t align = (char *)&align_data.u.imax - &align_data.c;

Dovresti quindi utilizzare il maggiore dell'allineamento richiesto (nell'esempio 16) e il alignvalore calcolato sopra.

Su (64 bit) Solaris 10, sembra che l'allineamento di base per il risultato malloc()sia un multiplo di 32 byte.
]

In pratica, gli allocatori allineati spesso accettano un parametro per l'allineamento anziché essere cablato. Quindi l'utente passerà nella dimensione della struttura a cui tiene (o la potenza minima di 2 maggiore o uguale a quella) e tutto andrà bene.

3) Usa ciò che la tua piattaforma fornisce: posix_memalignper POSIX, _aligned_mallocsu Windows.

4) Se si utilizza C11, l'opzione più pulita, portatile e concisa, è quella di utilizzare la funzione di libreria standard aligned_allocintrodotta in questa versione delle specifiche del linguaggio.


1
Sono d'accordo - penso che l'intento della domanda sia che il codice che libera il blocco di memoria avrebbe accesso solo al puntatore allineato a 16 byte "cotto".
Michael Burr,

1
Per una soluzione generale, hai ragione. Tuttavia, il modello di codice nella domanda mostra chiaramente entrambi.
Jonathan Leffler,

1
Certo, e in una buona intervista ciò che accade è che tu dai la tua risposta, quindi se l'intervistatore vuole vedere la mia risposta, cambiano la domanda.
Steve Jessop,

1
Mi oppongo all'utilizzo ASSERT(mem);per verificare i risultati di allocazione; assertserve per rilevare errori di programmazione e non mancanza di risorse di runtime.
hlovdal,

4
L'uso di binario e con a char *e a size_tcomporterà un errore. Dovresti usare qualcosa del genere uintptr_t.
Marko,


20

Ecco un approccio alternativo alla parte "arrotondamento". Non è la soluzione con il codice più brillante ma fa il lavoro, e questo tipo di sintassi è un po 'più facile da ricordare (inoltre funzionerebbe per valori di allineamento che non sono una potenza di 2). Il uintptr_tcast era necessario per placare il compilatore; l'aritmetica del puntatore non ama molto la divisione o la moltiplicazione.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);

2
In generale, dove hai 'unsigned long long', hai anche uintptr_t che è esplicitamente definito per essere abbastanza grande da contenere un puntatore di dati (vuoto *). Ma la tua soluzione ha davvero dei meriti se, per qualche ragione, avessi bisogno di un allineamento che non era una potenza di 2. Improbabile, ma possibile.
Jonathan Leffler,

@Andrew: il voto per questo tipo di sintassi è un po 'più facile da ricordare (inoltre funzionerebbe per valori di allineamento che non sono una potenza di 2) .
legends2k

19

Sfortunatamente, in C99 sembra abbastanza difficile garantire un allineamento di qualsiasi tipo in un modo che sia portatile attraverso qualsiasi implementazione C conforme a C99. Perché? Perché non è garantito che un puntatore sia "l'indirizzo byte", come si potrebbe immaginare con un modello di memoria piatta. Né la rappresentazione di uintptr_t è così garantita, che comunque è un tipo opzionale.

Potremmo conoscere alcune implementazioni che usano una rappresentazione per void * (e per definizione anche char * ) che è un semplice indirizzo byte, ma per C99 è opaco per noi programmatori. Un'implementazione potrebbe rappresentare un puntatore di un set { segmento , offset } in cui offset potrebbe avere un allineamento "chissà-cosa" nella realtà. Perché, un puntatore potrebbe anche essere una qualche forma di valore di ricerca della tabella hash o persino un valore di ricerca dell'elenco collegato. Potrebbe codificare le informazioni sui limiti.

In una recente bozza C1X per uno standard C, vediamo la parola chiave _Alignas . Questo potrebbe aiutare un po '.

L'unica garanzia che C99 ci offre è che le funzioni di allocazione della memoria restituiranno un puntatore adatto per l'assegnazione a un puntatore che punta a qualsiasi tipo di oggetto. Poiché non possiamo specificare l'allineamento degli oggetti, non possiamo implementare le nostre funzioni di allocazione con la responsabilità dell'allineamento in un modo ben definito e portatile.

Sarebbe bello essere sbagliato su questa affermazione.


C11 ha aligned_alloc(). (C ++ 11/14 / 1z ancora non ce l'hanno). _Alignas()e C ++ alignas()non fanno nulla per l'allocazione dinamica, ma solo per l'archiviazione automatica e statica (o layout di struttura).
Peter Cordes,

15

Sul fronte di imbottitura 16 vs 15 byte, il numero effettivo che è necessario aggiungere per ottenere un allineamento di N è max (0, NM) dove M è l'allineamento naturale dell'allocatore di memoria (ed entrambi sono potenze di 2).

Poiché l'allineamento di memoria minimo di qualsiasi allocatore è 1 byte, 15 = max (0,16-1) è una risposta prudente. Tuttavia, se sai che il tuo allocatore di memoria ti fornirà indirizzi allineati a 32 bit int (che è abbastanza comune), avresti potuto usare 12 come pad.

Questo non è importante per questo esempio, ma potrebbe essere importante su un sistema embedded con 12K di RAM in cui conta ogni singolo int salvato.

Il modo migliore per implementarlo se stai effettivamente cercando di salvare ogni byte possibile è come una macro in modo da poter alimentare il tuo allineamento di memoria nativa. Ancora una volta, questo è probabilmente utile solo per i sistemi embedded in cui è necessario salvare ogni byte.

Nell'esempio seguente, sulla maggior parte dei sistemi, il valore 1 va bene MEMORY_ALLOCATOR_NATIVE_ALIGNMENT, tuttavia per il nostro sistema embedded teorico con allocazioni allineate a 32 bit, il seguente potrebbe risparmiare un po 'di preziosa memoria:

#define MEMORY_ALLOCATOR_NATIVE_ALIGNMENT    4
#define ALIGN_PAD2(N,M) (((N)>(M)) ? ((N)-(M)) : 0)
#define ALIGN_PAD(N) ALIGN_PAD2((N), MEMORY_ALLOCATOR_NATIVE_ALIGNMENT)

8

Forse sarebbero stati soddisfatti con una conoscenza di memalign ? E come sottolinea Jonathan Leffler, ci sono due nuove funzioni preferibili da conoscere.

Oops, il fiorino mi ha battuto. Tuttavia, se leggi la pagina man a cui ho collegato, molto probabilmente capirai l'esempio fornito da un poster precedente.


1
Si noti che l'attuale versione (febbraio 2016) della pagina referenziata dice "La memalignfunzione è obsoleta e aligned_alloco posix_memaligndovrebbe essere utilizzata al suo posto". Non so cosa abbia detto nell'ottobre 2008, ma probabilmente non ha menzionato aligned_alloc()come è stato aggiunto a C11.
Jonathan Leffler,

5

Facciamo questo genere di cose tutto il tempo per Accelerate.framework, una libreria OS X / iOS fortemente vettorializzata, in cui dobbiamo prestare sempre attenzione all'allineamento. Ci sono alcune opzioni, una o due delle quali non ho mai visto prima.

Il metodo più veloce per un piccolo array come questo è semplicemente incollarlo nello stack. Con GCC / clang:

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

Nessun libero () richiesto. Di solito si tratta di due istruzioni: sottrarre 1024 dal puntatore dello stack, quindi AND il puntatore dello stack con -allineamento. Presumibilmente il richiedente aveva bisogno dei dati sull'heap perché la sua durata di vita dell'array ha superato lo stack o la ricorsione è al lavoro o lo spazio dello stack è un premio serio.

Su OS X / iOS tutte le chiamate a malloc / calloc / etc. sono sempre allineati a 16 byte. Se hai bisogno di 32 byte allineati per AVX, ad esempio, puoi usare posix_memalign:

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

Alcune persone hanno menzionato l'interfaccia C ++ che funziona in modo simile.

Non bisogna dimenticare che le pagine sono allineate a grandi potenze di due, quindi anche i buffer allineati alle pagine sono allineati a 16 byte. Pertanto, mmap () e valloc () e altre interfacce simili sono anche opzioni. mmap () ha il vantaggio che il buffer può essere allocato preinizializzato con qualcosa di diverso da zero, se lo si desidera. Poiché queste hanno dimensioni allineate alla pagina, non otterrete l'allocazione minima da queste e sarà probabilmente soggetta a un errore della macchina virtuale la prima volta che la toccate.

Cheesy: attiva la guardia malloc o simile. I buffer di dimensioni n * 16 byte come questo saranno n * 16 byte allineati, poiché la macchina virtuale viene utilizzata per rilevare i sovraccarichi e i suoi confini si trovano ai confini della pagina.

Alcune funzioni di Accelerate.framework accettano un buffer temporaneo fornito dall'utente da utilizzare come spazio scratch. Qui dobbiamo supporre che il buffer passato a noi sia disallineato e che l'utente stia attivamente cercando di rendere la nostra vita difficile per dispetto. (I nostri casi di test incollano una pagina di guardia subito prima e dopo il buffer temporaneo per sottolineare il dispetto.) Qui, restituiamo la dimensione minima necessaria per garantire un segmento allineato a 16 byte da qualche parte in esso, quindi allineare manualmente il buffer in seguito. Questa dimensione è desiderata_misura + allineamento - 1. Quindi, in questo caso è 1024 + 16-1 = 1039 byte. Quindi allineare così:

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

L'aggiunta di allineamento-1 sposta il puntatore oltre il primo indirizzo allineato e quindi ANDing con -allineamento (ad es. 0xfff ... ff0 per allineamento = 16) lo riporta indietro all'indirizzo allineato.

Come descritto da altri post, su altri sistemi operativi senza garanzie di allineamento a 16 byte, puoi chiamare malloc con dimensioni maggiori, mettere da parte il puntatore gratuitamente () in seguito, quindi allineare come descritto immediatamente sopra e utilizzare il puntatore allineato, tanto quanto descritto per il nostro caso buffer temporaneo.

Per quanto riguarda align_memset, questo è piuttosto sciocco. Per raggiungere un indirizzo allineato, è necessario eseguire il looping di un massimo di 15 byte, quindi procedere con gli archivi allineati successivamente con un possibile codice di pulizia alla fine. Puoi persino eseguire i bit di pulizia nel codice vettoriale, sia come archivi non allineati che si sovrappongono alla regione allineata (a condizione che la lunghezza sia almeno la lunghezza di un vettore) o usando qualcosa come movmaskdqu. Qualcuno è solo pigro. Tuttavia, è probabilmente una ragionevole domanda di intervista se l'intervistatore vuole sapere se sei a tuo agio con stdint.h, operatori bit a bit e fondamenti della memoria, quindi l'esempio inventato può essere perdonato.


5

Sono sorpreso di Noone votato fino Shao 's risposta che, a quanto mi risulta, non è possibile fare ciò che è chiesto in serie C99, dal momento che la conversione di un puntatore ad un tipo integrale formalmente è un comportamento indefinito. (A parte lo standard che consente la conversione di uintptr_t<-> void*, ma lo standard non sembra consentire alcuna manipolazione del uintptr_tvalore e poi riconvertirlo.)


Non è necessario che esista un tipo uintptr_t o che i suoi bit abbiano alcuna relazione con i bit nel puntatore sottostante. Se si dovesse allocare eccessivamente l'archiviazione, archiviare il puntatore come unsigned char* myptr; e quindi calcolare `mptr + = (16- (uintptr_t) my_ptr) e 0x0F, il comportamento verrebbe definito su tutte le implementazioni che definiscono my_ptr, ma se il puntatore risultante sarebbe allineato dipenderebbe dalla mappatura tra bit e indirizzi uintptr_t.
Supercat

3

l'utilizzo di memalign, Aligned-Memory-Blocks potrebbe essere una buona soluzione al problema.


Si noti che l'attuale versione (febbraio 2016) della pagina referenziata dice "La memalignfunzione è obsoleta e aligned_alloco posix_memaligndovrebbe essere utilizzata al suo posto". Non so cosa abbia detto nell'ottobre 2010.
Jonathan Leffler,

3

La prima cosa che mi è venuta in mente durante la lettura di questa domanda è stata definire una struttura allineata, istanziarla e quindi indicarla.

C'è un motivo fondamentale che mi manca dal momento che nessun altro l'ha suggerito?

Come sidenote, dato che ho usato un array di caratteri (supponendo che il carattere di sistema sia 8 bit (cioè 1 byte)), non vedo la necessità del __attribute__((packed))necessariamente (correggimi se sbaglio), ma l'ho messo in ogni modo.

Funziona su due sistemi su cui l'ho provato, ma è possibile che ci sia un'ottimizzazione del compilatore che non sono consapevole di darmi falsi positivi rispetto all'efficacia del codice. Ho usato gcc 4.9.2su OSX e gcc 5.2.1su Ubuntu.

#include <stdio.h>
#include <stdlib.h>

int main ()
{

   void *mem;

   void *ptr;

   // answer a) here
   struct __attribute__((packed)) s_CozyMem {
       char acSpace[16];
   };

   mem = malloc(sizeof(struct s_CozyMem));
   ptr = mem;

   // memset_16aligned(ptr, 0, 1024);

   // Check if it's aligned
   if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
   else printf("Rubbish.\n");

   // answer b) here
   free(mem);

   return 1;
}

1

MacOS X specifico:

  1. Tutti i puntatori allocati con malloc sono allineati di 16 byte.
  2. C11 è supportato, quindi puoi semplicemente chiamare align_malloc (16, dimensione).

  3. MacOS X seleziona il codice ottimizzato per i singoli processori all'avvio per memset, memcpy e memmove e quel codice utilizza trucchi di cui non hai mai sentito parlare per renderlo veloce. Probabilità del 99% che memset sia più veloce di qualsiasi memset scritto a mano16 il che rende inutile l'intera domanda.

Se vuoi una soluzione portatile al 100%, prima di C11 non ce n'è. Perché non esiste un modo portatile per testare l'allineamento di un puntatore. Se non deve essere portatile al 100%, puoi usarlo

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

Ciò presuppone che l'allineamento di un puntatore sia memorizzato nei bit più bassi quando si converte un puntatore in int senza segno. La conversione in unsigned int perde informazioni ed è definita dall'implementazione, ma non importa perché non riconvertiamo il risultato in un puntatore.

La parte orribile è ovviamente che il puntatore originale deve essere salvato da qualche parte per chiamare free () con esso. Quindi, nel complesso, dubiterei davvero della saggezza di questo design.


1
Dove ti trovi aligned_mallocin OS X? Sto usando Xcode 6.1 e non è definito da nessuna parte nell'SDK di iOS, né è dichiarato da nessuna parte in /usr/include/*.
Todd Lehman,

Idem per XCode 7.2 su El Capitan (Mac OS X 10.11.3). La funzione C11 è, in ogni caso aligned_alloc(), ma neanche quella è dichiarata. Da GCC 5.3.0, ottengo i messaggi interessanti alig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror]e alig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’. Il codice includeva effettivamente <stdlib.h>, ma né -std=c11né ha -std=gnu11modificato i messaggi di errore.
Jonathan Leffler,

0

Puoi anche aggiungere circa 16 byte e quindi spingere il ptr originale a 16 bit allineato aggiungendo il (16-mod) come sotto il puntatore:

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}

0

Se ci sono vincoli che, non è possibile sprecare un singolo byte, questa soluzione funziona: Nota: c'è un caso in cui questo può essere eseguito all'infinito: D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);

È molto probabile che se si alloca e quindi si libera un blocco di N byte e quindi si richiede un altro blocco di N byte, il blocco originale verrà nuovamente restituito. Quindi è molto probabile che un ciclo infinito se la prima allocazione non soddisfi i requisiti di allineamento. Ovviamente, ciò evita di sprecare un singolo byte al costo di sprecare molti cicli della CPU.
Jonathan Leffler,

Sei sicuro che l' %operatore sia definito void*in modo significativo?
Ajay Brahmakshatriya,

0

Per la soluzione ho usato un concetto di padding che allinea la memoria e non spreca la memoria di un singolo byte.

Se esistono dei vincoli, non è possibile sprecare un singolo byte. Tutti i puntatori allocati con malloc sono allineati di 16 byte.

C11 è supportato, quindi puoi semplicemente chiamare aligned_alloc (16, size).

void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);

1
Su molti sistemi a 64 bit, il puntatore restituito da malloc()è effettivamente allineato su un limite di 16 byte, ma nulla in uno standard garantisce che - sarà semplicemente sufficientemente ben allineato per qualsiasi uso e su molti sistemi a 32 bit che si allineano su un Il limite di 8 byte è sufficiente e per alcuni è sufficiente un limite di 4 byte.
Jonathan Leffler,

0
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

Spero che questa sia l'implementazione più semplice, fammi sapere i tuoi commenti.


-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);

Penso che ci sia un problema con questo perché la tua aggiunta punterà a una posizione che non viene assegnata - Non sono sicuro di come abbia funzionato sul tuo.
risultati

@Sam Dovrebbe essere add += 16 - (add % 16). (2 - (2 % 16)) == 0.
SS Anne,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.