Esiste un modo standard o un'alternativa standard per impacchettare una struttura in c?

12

Quando la programmazione in CI ha trovato inestimabile il pack delle strutture usando l'attributo GCC% __attribute__((__packed__)) così posso convertire facilmente un blocco strutturato di memoria volatile in una matrice di byte da trasmettere su un bus, salvato in memoria o applicato a un blocco di registri. Le strutture pacchettizzate garantiscono che, se trattate come una matrice di byte, non contengono alcuna spaziatura, che è allo stesso tempo dispendiosa, un possibile rischio per la sicurezza ed eventualmente incompatibile con l'interfacciamento dell'hardware.

Non esiste uno standard per l'impacchettamento di strutture che funzioni in tutti i compilatori C? In caso contrario, sono un outlier nel pensare che questa sia una caratteristica critica per la programmazione dei sistemi? I primi utenti del linguaggio C non hanno trovato la necessità di impacchettare le strutture o c'è qualche tipo di alternativa?

    
posta satur9nine 12.01.2016 - 22:17
fonte

4 risposte

12

In una struttura, ciò che importa è l'offset di ciascun membro dall'indirizzo di ogni istanza di struct. Non tanto è il problema di quanto siano strette le cose.

Un array, tuttavia, ha importanza nel modo in cui è "imballato". La regola in C è che ogni elemento dell'array è esattamente N byte dal precedente, dove N è il numero di byte utilizzati per memorizzare quel tipo.

Ma con una struttura, non c'è un tale bisogno di uniformità.

Ecco un esempio di uno schema di imballaggio strano:

Freescale (che produce microcontrollori per autoveicoli) crea un micro con un coprocessore Time Processing Unit (google per eTPU o TPU). Ha due dimensioni di dati nativi, 8 bit e 24 bit e tratta solo con numeri interi.

Questa struttura:

struct a
{
  U24 elementA;
  U24 elementB;
};

vedrà ogni U24 memorizzato il proprio blocco a 32 bit, ma solo nell'area di indirizzi più alta.

Questa:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

avrà due U24 memorizzati in blocchi adiacenti a 32 bit, e l'U8 sarà memorizzato nel "buco" di fronte al primo U24, elementA .

Ma puoi dire al compilatore di mettere tutto nel proprio blocco a 32 bit, se vuoi; è più costoso su RAM ma utilizza meno istruzioni per gli accessi.

"packing" non significa "pack tightly" - significa solo qualche schema per organizzare elementi di una struct rispetto all'offset.

Non esiste uno schema generico, è compilatore + dipendente dall'architettura.

    
risposta data 12.01.2016 - 23:50
fonte
6

When programming in C I have found it invaluable to pack structs using GCCs __attribute__((__packed__)) [...]

Dato che menzioni __attribute__((__packed__)) , presumo che la tua intenzione sia quella di eliminare tutto il padding entro un struct (fai in modo che ogni membro abbia un allineamento di 1 byte).

Is there no standard for packing structs that works in all C compilers?

... e la risposta è "no". Il riempimento e l'allineamento dei dati relativi a una struttura (e agli array contigui di strutture nello stack o heap) esistono per una ragione importante. Su molte macchine, l'accesso alla memoria non allineato può portare a una penalità prestazionale potenzialmente significativa (sebbene diventi meno su un hardware più recente). In alcuni scenari rari, l'accesso alla memoria disallineato porta a un errore del bus irrecuperabile (potrebbe persino danneggiare l'intero sistema operativo).

Dato che lo standard C è incentrato sulla portabilità, non ha senso avere un modo standard per eliminare tutto il padding in una struttura e consentire semplicemente che i campi arbitrari siano disallineati, poiché farlo rischia potenzialmente di rendere il codice C non-portatile .

Il modo più sicuro e più portabile per inviare tali dati a una fonte esterna in modo da eliminare tutto il padding è serializzare su / da flussi di byte invece di provare semplicemente a inviare il contenuto della memoria grezza del structs . Ciò impedisce anche al tuo programma di subire penalizzazioni prestazionali al di fuori di questo contesto di serializzazione e ti permetterà anche di aggiungere liberamente nuovi campi a una struct senza buttare via e glitching dell'intero software. Ti darà anche spazio per affrontare endianness e cose del genere se mai dovesse diventare un problema.

C'è un modo per eliminare tutto il padding senza raggiungere le direttive specifiche del compilatore, sebbene sia applicabile solo se l'ordine relativo tra i campi non ha importanza. Dato qualcosa di simile:

struct Foo
{
    double x;  // assume 8-byte alignment
    char y;    // assume 1-byte alignment
               // 7 bytes of padding for first field
};

... abbiamo bisogno del padding per l'accesso alla memoria allineato relativo all'indirizzo della struttura che contiene questi campi, in questo modo:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......

... dove . indica padding. Ogni x deve essere allineato a un limite di 8 byte per le prestazioni (e talvolta anche un comportamento corretto).

Puoi eliminare il padding in modo portatile usando una rappresentazione SoA (structure of array) in questo modo (supponiamo di aver bisogno dell'8% di istanze diFoo):

struct Foos
{
   double x[8];
   char y[8];
};

Abbiamo effettivamente demolito la struttura. In questo caso, la rappresentazione della memoria diventa così:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______

... e questo:

01234567
yyyyyyyy

... non più sovraccarico di padding e senza coinvolgere l'accesso alla memoria non allineato poiché non stiamo più accedendo a questi campi dati come offset di un indirizzo di struttura, ma come offset di un indirizzo di base per ciò che effettivamente è un array .

Ciò comporta anche il vantaggio di essere più veloce per l'accesso sequenziale a causa della minore quantità di dati da consumare (non più irrilevante riempimento nel mix per rallentare la velocità di consumo dei dati della macchina) e anche del potenziale per il vettore di vettorializzare elaborazione molto banale.

Il rovescio della medaglia è che è un PITA da codificare. È anche potenzialmente meno efficiente per l'accesso casuale con il passo più lungo tra i campi, dove spesso i rappresentanti AoS o AoSoA fanno meglio. Ma questo è un modo standard per eliminare l'imbottitura e imballare le cose il più strettamente possibile senza rovinare l'allineamento di tutto.

    
risposta data 14.01.2016 - 21:39
fonte
5

Nessuna architettura è uguale, basta attivare l'opzione a 32 bit su un modulo e vedere cosa succede quando si utilizza lo stesso codice sorgente e lo stesso compilatore. L'ordine dei byte è un'altra limitazione ben nota. Getta la rappresentazione in virgola mobile e i problemi peggiorano. L'uso di Packing per inviare dati binari non è portabile. Per standardizzarlo in modo che fosse praticamente utilizzabile, è necessario ridefinire le specifiche del linguaggio C.

Sebbene sia comune, usare Pack per inviare dati binari è una cattiva idea se si desidera sicurezza, portabilità o longevità dei dati. Quanto spesso leggi un blob binario da una fonte nel tuo programma. Con quale frequenza controlli tutti i valori sono sani di mente, che un hacker o un cambio di programma non ha "ottenuto" i dati? Nel momento in cui hai codificato una routine di controllo, potresti anche utilizzare le routine di importazione ed esportazione.

    
risposta data 13.01.2016 - 08:18
fonte
0

Un'alternativa molto comune è "named padding":

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

Questo non presuppone che la struttura non venga riempita a 8 byte.

    
risposta data 13.01.2016 - 10:33
fonte

Leggi altre domande sui tag