#pragma pack effect


233

Mi chiedevo se qualcuno potesse spiegarmi cosa fa la #pragma packdichiarazione del preprocessore e, soprattutto, perché si vorrebbe usarla.

Ho dato un'occhiata alla pagina MSDN , che offriva alcune informazioni, ma speravo di sentire di più dalle persone con esperienza. L'ho già visto in codice, anche se non riesco più a trovare dove.


1
Forza un particolare allineamento / impacchettamento di una struttura, ma come tutte le #pragmadirettive sono definite dall'implementazione.
dreamlax,

A mod s = 0dove A è l'indirizzo e s è la dimensione del tipo di dati; questo controlla se un dato non è disallineato.
legends2k

Risposte:


422

#pragma packindica al compilatore di impacchettare i membri della struttura con un allineamento particolare. 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 delle dimensioni del tipo). Ciò evita la penalità di prestazione (o errore assoluto) su alcune architetture associate all'accesso a variabili non 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 disporre la struttura 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 di #pragma(per quanto ne so) è quando si lavora con dispositivi hardware in cui è necessario assicurarsi che il compilatore non inserisca il riempimento nei dati e che ciascun membro segua il precedente. Con #pragma pack(1), la struttura sopra sarebbe strutturata in questo modo:

|   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 in questo modo:

|   1   |   2   | 

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

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

Anche l'ordine delle variabili in struct è importante. Con variabili ordinate come segue:

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

e con #pragma pack(2), la struttura sarebbe disposta in questo modo:

|   1   |   2   | 

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

e sizeOf(Test)sarebbe 3 × 2 = 6.


76
Potrebbe valere la pena aggiungere gli svantaggi dell'imballaggio. (Nel migliore dei casi, gli accessi agli oggetti non allineati sono lenti , ma causeranno errori su alcune piattaforme.)
jalf

11
Sembra che gli allineamenti "penalità di prestazione" menzionati potrebbero effettivamente essere un vantaggio su alcuni sistemi danluu.com/3c-conflict .

6
@Pacerier Non proprio. Quel post parla di un allineamento abbastanza estremo (allineamento sui confini 4KB). La CPU prevede alcuni allineamenti minimi per vari tipi di dati, ma quelli richiedono, nel peggiore dei casi, un allineamento di 8 byte (senza contare i tipi di vettore che potrebbero richiedere un allineamento di 16 o 32 byte). Non allinearsi su questi confini in genere ti dà un notevole impatto sulle prestazioni (perché un carico potrebbe dover essere fatto come due operazioni anziché una), ma il tipo è ben allineato o non lo è. Un allineamento più rigoroso di quello non ti compra nulla (e rovina l'utilizzo della cache
jalf

6
In altre parole, un doppio si aspetta di essere su un limite di 8 byte. Metterlo su un limite di 7 byte comprometterà le prestazioni. Ma metterlo su un limite di 16, 32, 64 o 4096 byte non ti compra nulla al di sopra di quello che il limite di 8 byte ti ha già dato. Otterrai le stesse prestazioni dalla CPU, migliorando notevolmente l'utilizzo della cache per i motivi indicati in quel post.
jalf

4
Quindi la lezione non è "impaccare è benefico" (l'imballaggio viola il naturale allineamento dei tipi, quindi fa male alle prestazioni), ma semplicemente "non allineare eccessivamente oltre ciò che è richiesto"
jalf

27

#pragmaviene utilizzato per inviare al compilatore messaggi non portatili (come solo in questo compilatore). Cose come la disabilitazione di alcuni avvisi e le strutture di imballaggio sono motivi comuni. La disabilitazione di avvisi specifici è particolarmente utile se si compila con gli avvisi quando il flag degli errori è attivato.

#pragma packin particolare viene utilizzato per indicare che la struttura che viene imballata non dovrebbe avere i suoi membri allineati. È utile quando si dispone di un'interfaccia mappata in memoria su un componente hardware e è necessario essere in grado di controllare esattamente dove puntano i diversi membri della struttura. In particolare, non si tratta di una buona ottimizzazione della velocità, poiché la maggior parte delle macchine è molto più veloce nel gestire i dati allineati.


17
Per annullare in seguito, procedi come segue: #pragma pack (push, 1) e #pragma pack (pop)
malhal

16

Indica al compilatore il confine a cui allineare gli oggetti in una struttura. Ad esempio, se ho qualcosa come:

struct foo { 
    char a;
    int b;
};

Con una tipica macchina a 32 bit, normalmente "vorresti" avere 3 byte di padding tra ae bcosì che batterrerà a un limite di 4 byte per massimizzare la sua velocità di accesso (e questo è ciò che accadrà in genere per impostazione predefinita).

Se, tuttavia, devi abbinare una struttura definita esternamente, vuoi assicurarti 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 riempimento tra i membri - se la definizione della struttura include il riempimento tra i membri, lo inserisci esplicitamente (ad esempio, in genere con i membri nominati unusedNo ignoreN, o qualcosa del genere) ordine).


"Normalmente" vorresti "avere 3 byte di spaziatura tra aeb, in modo che b raggiunga un limite di 4 byte per massimizzare la sua velocità di accesso" - in che modo avere 3 byte di spaziatura massimizzerebbe la velocità di accesso?
Ashwin,

8
@Ashwin: il posizionamento bsu un limite di 4 byte significa che il processore può caricarlo emettendo un singolo carico di 4 byte. Anche se dipende in qualche modo dal processore, se ha un limite strano c'è una buona probabilità che caricarlo richieda al processore di impartire due istruzioni di caricamento separate, quindi utilizzare un cambio per mettere insieme quei pezzi. La penalità tipica è dell'ordine del carico 3 volte più lento di quell'articolo.
Jerry Coffin,

... se si osserva il codice assembly per la lettura di int allineati e non allineati, la lettura allineata è in genere un singolo mnemonico. La lettura non allineata può essere facilmente di 10 linee di assemblaggio poiché riunisce l'int int, selezionandolo byte per byte e posizionandolo nelle posizioni corrette del registro.
SF.

2
@SF .: Può essere - ma anche quando non lo è, non essere fuorviato - su una CPU x86 (per un esempio ovvio) le operazioni vengono eseguite in hardware, ma si ottiene ancora più o meno lo stesso insieme di operazioni e rallentamento.
Jerry Coffin,

8

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 in un indirizzo non divisibile per 4 richiede almeno un ciclo CPU aggiuntivo su un processore a 32 bit. Quindi, se hai ad esempio tre membri char char a, b, c;, in realtà tendono a occupare 6 o 12 byte di memoria.

#pragmaconsente di ignorare ciò per ottenere un utilizzo dello spazio più efficiente, a scapito della velocità di accesso o per coerenza dei dati memorizzati tra destinazioni del compilatore diverse. Mi sono divertito molto con questo passaggio da codice a 16 bit a 32 bit; Mi aspetto che il porting su codice a 64 bit provochi gli stessi tipi di mal di testa per alcuni codici.


In realtà, char a,b,c;in genere richiedono 3 o 4 byte di memoria (almeno su x86), poiché il loro requisito di allineamento è di 1 byte. Se non lo fosse, come gestiresti char str[] = "foo";? L'accesso a charè sempre una semplice maschera fetch-shift, mentre l'accesso a intpuò essere fetch-fetch-merge o solo fetch, a seconda che sia allineato o meno. intha (su x86) un allineamento a 32 bit (4 byte) perché altrimenti otterresti (diciamo) metà intin uno DWORDe metà nell'altro, e ciò richiederebbe due ricerche.
Tim Čas,

3

Il compilatore potrebbe allineare i membri nelle strutture per ottenere le massime prestazioni su una determinata piattaforma. #pragma packdirettiva consente di controllare tale allineamento. Di solito dovresti lasciarlo per impostazione predefinita per prestazioni ottimali. Se è necessario passare una struttura alla macchina remota, in genere verrà utilizzata #pragma pack 1per escludere qualsiasi allineamento indesiderato.


2

Un compilatore può posizionare i membri della struttura su particolari limiti di byte per motivi di prestazioni su una particolare architettura. Ciò può lasciare un'imbottitura inutilizzata tra i membri. L'imballaggio della struttura costringe i membri ad essere contigui.

Ciò può essere importante, ad esempio, se si richiede che una struttura sia conforme a un determinato file o formato di comunicazione in cui i dati necessari sono in posizioni specifiche all'interno di una sequenza. Tuttavia, tale utilizzo non affronta i problemi di endianità, quindi sebbene utilizzato, potrebbe non essere portatile.

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


1

Probabilmente vorresti usarlo solo se stavi codificando un determinato hardware (ad esempio un dispositivo mappato in memoria) che aveva requisiti rigorosi per l'ordinamento e l'allineamento dei registri.

Tuttavia, questo sembra uno strumento piuttosto schietto per raggiungere questo scopo. Un approccio migliore sarebbe codificare un mini-driver nell'assemblatore e dargli un'interfaccia di chiamata C piuttosto che armeggiare con questo pragma.


In realtà lo uso parecchio per risparmiare spazio su grandi tavoli a cui non si accede frequentemente. Lì serve solo per risparmiare spazio e non per nessun allineamento rigoroso. (Ti ho appena votato, a proposito. Qualcuno ti ha dato un voto negativo.)
Todd Lehman,

1

L'ho usato prima nel codice, anche se solo per interfacciarsi con il codice legacy. Questa era un'applicazione Mac OS X Cocoa che aveva bisogno di caricare i file delle preferenze da una versione precedente di Carbon (che era a sua volta retrocompatibile con la versione originale di M68k System 6.5 ... hai capito). I file delle preferenze nella versione originale erano un dump binario di una struttura di configurazione, che utilizzava #pragma pack(1)per evitare di occupare spazio extra e salvare junk (ovvero i byte di riempimento che sarebbero altrimenti nella struttura).

Gli autori originali del codice avevano anche usato #pragma pack(1)per memorizzare strutture usate come messaggi nella comunicazione tra processi. Penso che il motivo qui sia stato quello di evitare la possibilità di dimensioni di riempimento sconosciute o modificate, poiché il codice a volte guardava una parte specifica della struttura del messaggio contando un numero di byte dall'inizio (ewww).


1

Ho visto persone usarlo per assicurarsi che una struttura occupi un'intera riga della cache per impedire la falsa condivisione in un contesto multithread. Se si dispone di un gran numero di oggetti che verranno impacchettati in modo predefinito per impostazione predefinita, si potrebbe risparmiare memoria e migliorare le prestazioni della cache per comprimerli più stretti, sebbene l'accesso alla memoria non allineato di solito rallenti le cose, quindi potrebbe esserci un aspetto negativo.


0

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

#include <stdio.h>

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

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


No è sbagliato. Se osservi la posizione in memoria di b.two, non è un byte dopo b.one (il compilatore può (e spesso farà) allineare b.two in modo che sia allineato all'accesso alle parole). Per a.two, è esattamente un byte dopo a.one. Se hai bisogno di accedere a a.two come int breve, dovresti avere 2 alternative, o utilizzare un'unione (ma questo di solito fallisce se hai problemi di endianness), o decomprimere / convertire per codice (usando la funzione ntohX appropriata)
xryl669

1
sizeofrestituisce un size_tche deve essere stampato usando%zu . L'uso
dell'identificatore di

0

Perché uno vuole usarlo?

Ridurre la memoria della struttura

Perché non si dovrebbe usarlo?

  1. Ciò può comportare una riduzione delle prestazioni, poiché alcuni sistemi funzionano meglio su dati allineati
  2. Alcune macchine non riusciranno a leggere i dati non allineati
  3. Il codice non è portatile
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.