# effetto pack pack

Mi stavo chiedendo se qualcuno potesse spiegarmi cosa fa la dichiarazione del preprocessore del #pragma pack e, cosa ancora più importante, perché uno vorrebbe usarlo.

Ho controllato la pagina MSDN , che mi ha offerto alcune informazioni, ma speravo di sentire altro da persone con esperienza. L’ho visto in codice prima, anche se non riesco a trovare dove.

#pragma pack indica al compilatore di impacchettare i membri della struttura con un particolare allineamento. La maggior parte dei compilatori, quando si dichiara una struttura, inserirà il riempimento tra i membri per assicurarsi che siano allineati agli indirizzi appropriati in memoria (di solito un multiplo della dimensione del tipo). Ciò evita la penalità delle prestazioni (o l’errore totale) su alcune architetture associate all’accesso alle variabili che non sono allineate correttamente. Ad esempio, dati interi a 4 byte e la seguente struttura:

 struct Test { char AA; int BB; char CC; }; 

Il compilatore potrebbe scegliere di posizionare la struct in memoria in questo modo:

 | 1 | 2 | 3 | 4 | | AA(1) | pad.................. | | BB(1) | BB(2) | BB(3) | BB(4) | | CC(1) | pad.................. | 

e sizeof(Test) sarebbe 4 × 3 = 12, anche se contiene solo 6 byte di dati. Il caso d’uso più comune per #pragma (a mia conoscenza) è quando si lavora con dispositivi hardware in cui è necessario assicurarsi che il compilatore non inserisca il padding nei dati e che ogni membro segua il precedente. Con #pragma pack(1) , la struttura sopra sarebbe strutturata come questa:

 | 1 | | AA(1) | | BB(1) | | BB(2) | | BB(3) | | BB(4) | | CC(1) | 

E sizeof(Test) sarebbe 1 × 6 = 6.

Con #pragma pack(2) , la struttura sopra sarebbe strutturata come questa:

 | 1 | 2 | | AA(1) | pad.. | | BB(1) | BB(2) | | BB(3) | BB(4) | | CC(1) | pad.. | 

E sizeof(Test) sarebbe 2 × 4 = 8.

#pragma è usato per inviare messaggi non portabili (come in questo solo compilatore) al compilatore. Cose come la disabilitazione di alcuni avvisi e strutture di imballaggio sono ragioni comuni. La disabilitazione di avvertimenti specifici è particolarmente utile se si compila con gli avvisi come flag di errore triggersto.

#pragma pack particolare viene utilizzato per indicare che la struttura che viene compresso non deve avere i suoi membri allineati. È utile quando si dispone di un’interfaccia mappata in memoria su un pezzo di hardware e occorre essere in grado di controllare esattamente dove puntano i diversi membri della struttura. In particolare, non è una buona ottimizzazione della velocità, dal momento che la maggior parte delle macchine è molto più veloce nel gestire i dati allineati.

Indica al compilatore il limite per allineare gli oggetti in una struttura. Ad esempio, se ho qualcosa di simile:

 struct foo { char a; int b; }; 

Con una tipica macchina a 32 bit, normalmente si “desidera” avere 3 byte di padding tra b modo che b atterra su un limite di 4 byte per massimizzare la sua velocità di accesso (e questo è ciò che normalmente accade di default ).

Se, tuttavia, devi abbinare una struttura definita esternamente, assicurati che il compilatore esponga la tua struttura esattamente secondo quella definizione esterna. In questo caso, puoi dare al compilatore un #pragma pack(1) per dirgli di non inserire alcun padding tra i membri – se la definizione della struttura include il padding tra i membri, lo inserisci esplicitamente (ad esempio, tipicamente con i membri chiamati unusedN o ignoreN , o qualcosa su quell’ordine).

  1. # pragma pack (n) imposta semplicemente il nuovo allineamento.
  2. # pragma pack () imposta l’allineamento su quello che era in vigore al momento della compilazione.
  3. # pragma pack (push [, n]) spinge l’impostazione di allineamento corrente su uno stack interno e quindi imposta facoltativamente il nuovo allineamento.
  4. # pragma pack (pop) ripristina l’impostazione di allineamento a quella salvata nella parte superiore dello stack interno (e rimuove quella voce dello stack). Si noti che #pragma pack ([n]) non influenza questo stack interno; quindi è ansible avere #pragma pack (push) seguito da più istanze di #pragma pack (n) e finalizzato da un singolo pacchetto #pragma (pop).

Esempi:

 #pragma pack(push, 1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; }; #pragma pack(pop) //back to whatever the previous packing mode was Or #pragma pack(1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; }; #pragma pack() //back to whatever the previous packing mode was Or #pragma pack(1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; }; 

Gli elementi di dati (ad esempio membri di classi e strutture) sono in genere allineati sui limiti WORD o DWORD per i processori di generazione corrente al fine di migliorare i tempi di accesso. Il recupero di un DWORD su un indirizzo che non è divisibile per 4 richiede almeno un ciclo CPU aggiuntivo su un processore a 32 bit. Quindi, se si dispone ad esempio di tre membri char a, b, c; , in realtà tendono a prendere 6 o 12 byte di spazio.

#pragma consente di ignorare questo per ottenere un utilizzo dello spazio più efficiente, a scapito della velocità di accesso, o per la coerenza dei dati memorizzati tra diversi target del compilatore. Mi sono divertito molto con questa transizione da codice 16 bit a 32 bit; Mi aspetto che il porting su un codice a 64 bit causerà lo stesso tipo di mal di testa per qualche codice.

Un compilatore può posizionare i membri della struttura su limiti di byte particolari per ragioni di prestazioni su una particolare architettura. Questo potrebbe lasciare il pad inutilizzato tra i membri. La struttura della struttura costringe i membri ad essere contigui.

Questo può essere importante ad esempio se si richiede una struttura per conformarsi a un particolare file o formato di comunicazione in cui i dati necessari per i dati devono essere in posizioni specifiche all’interno di una sequenza. Tuttavia, tale utilizzo non affronta problemi di endianità, quindi, sebbene usato, potrebbe non essere portatile.

Può anche sovrapporre esattamente la struttura interna del registro di alcuni dispositivi I / O come ad esempio un controller UART o USB, in modo che l’accesso al registro avvenga attraverso una struttura piuttosto che con indirizzi diretti.

Il compilatore potrebbe allineare i membri nelle strutture per ottenere le massime prestazioni su una determinata piattaforma. #pragma pack direttiva #pragma pack consente di controllare tale allineamento. Di solito dovresti lasciarlo per impostazione predefinita per prestazioni ottimali. Se è necessario passare una struttura al computer remoto, in genere si utilizzerà #pragma pack 1 per escludere qualsiasi allineamento indesiderato.

Probabilmente vorrai usarlo solo se stai codificando su un determinato hardware (ad esempio un dispositivo mappato in memoria) che aveva requisiti rigorosi per l’ordine e l’allineamento del registro.

Tuttavia, questo sembra uno strumento piuttosto smussato per raggiungere quella fine. Un approccio migliore sarebbe quello di codificare un mini-driver in assemblatore e dargli un’interfaccia di chiamata C piuttosto che armeggiare con questo pragma.

L’ho usato in codice prima, anche se solo per interfacciarlo con il codice legacy. Questa era un’applicazione Coco OS di Mac OS X che aveva bisogno di caricare i file delle preferenze da una precedente versione di Carbon (che era a sua volta compatibile con la versione originale di M68k System 6.5 … si ottiene l’idea). I file delle preferenze nella versione originale erano un dump binario di una struttura di configurazione, che utilizzava il #pragma pack(1) per evitare di occupare spazio extra e risparmiare posta indesiderata (cioè i byte di riempimento che altrimenti sarebbero nella struttura).

Gli autori originali del codice utilizzavano anche #pragma pack(1) per archiviare le strutture utilizzate come messaggi nella comunicazione tra processi. Penso che la ragione qui sia stata quella di evitare la possibilità di dimensioni di padding sconosciute o modificate, dato che il codice a volte guardava una porzione specifica della struttura del messaggio contando un numero di byte dall’inizio (ewww).

Ho visto persone che lo usano per assicurarsi che una struttura prenda un’intera riga della cache per impedire la condivisione errata in un contesto con multithreading. Se si dispone di un numero elevato di oggetti che verranno compressi in modo approssimativo, è ansible risparmiare memoria e migliorare le prestazioni della cache per comprimerle in modo più stretto, sebbene l’accesso alla memoria non allineato di solito rallenti le cose, quindi potrebbe esserci uno svantaggio.

Si noti che esistono altri modi per ottenere la coerenza dei dati offerta da #pragma pack (ad esempio alcune persone usano #pragma pack (1) per le strutture che devono essere inviate attraverso la rete). Ad esempio, vedere il seguente codice e il suo output successivo:

 #include  struct a { char one; char two[2]; char eight[8]; char four[4]; }; struct b { char one; short two; long int eight; int four; }; int main(int argc, char** argv) { struct a twoa[2] = {}; struct b twob[2] = {}; printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b)); printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob)); } 

L’output è il seguente: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

Si noti come la dimensione della struct a sia esattamente ciò che è il conteggio dei byte, ma struct b ha aggiunto padding (vedere questo per i dettagli sul padding). In questo modo, contrariamente al pacchetto #pragma, puoi avere il controllo della conversione del “formato wire” nei tipi appropriati. Ad esempio, “char two [2]” in “short int” eccetera.