Le strutture C con gli stessi tipi di membri hanno lo stesso layout in memoria?

In sostanza, se ho

typedef struct { int x; int y; } A; typedef struct { int h; int k; } B; 

e ho A a , lo standard C garantisce che ((B*)&a)->k è lo stesso di ay ?

Le strutture C con gli stessi tipi di membri hanno lo stesso layout in memoria?

Quasi sì. Abbastanza vicino per me.

Dal n1516, paragrafo 6.5.2.3, paragrafo 6:

… se un’unione contiene diverse strutture che condividono una sequenza iniziale comune …, e se l’object unione contiene attualmente una di queste strutture, è consentito ispezionare la parte iniziale comune di ognuna di esse ovunque che sia una dichiarazione del il tipo completato dell’unione è visibile. Due strutture condividono una sequenza iniziale comune se i membri corrispondenti hanno tipi compatibili (e, per i campi bit, le stesse larghezze) per una sequenza di uno o più membri iniziali.

Questo significa che se hai il seguente codice:

 struct a { int x; int y; }; struct b { int h; int k; }; union { struct aa; struct bb; } u; 

Se assegni a ua , lo standard dice che puoi leggere i valori corrispondenti da ub . Estende i limiti di plausibilità per suggerire che struct a e struct b possono avere layout diversi, data questa esigenza. Un tale sistema sarebbe patologico all’estremo.

Ricorda che lo standard garantisce anche che:

  • Le strutture non sono mai rappresentazioni di trappole.

  • Gli indirizzi dei campi in una struttura aumentano ( ax è sempre prima di ay ).

  • L’offset del primo campo è sempre zero.

Tuttavia, e questo è importante!

Hai riformulato la domanda,

lo standard C garantisce che ((B*)&a)->k è uguale a ay?

No! E afferma molto esplicitamente che non sono la stessa cosa!

 struct a { int x; }; struct b { int x; }; int test(int value) { struct aa; ax = value; return ((struct b *) &a)->x; } 

Questa è una violazione di aliasing.

Piggybacking sull’altro risponde con un avvertimento sulla sezione 6.5.2.3. Apparentemente c’è qualche discussione sulla formulazione esatta di anywhere that a declaration of the completed type of the union is visible , e almeno GCC non la implementa come scritto . Ci sono alcuni rapporti sui difetti tangenziali CW qui e qui con i commenti di follow-up del comitato.

Recentemente ho provato a scoprire come altri compilatori (in particolare GCC 4.8.2, ICC 14 e clang 3.4) interpretano questo usando il seguente codice dallo standard:

 // Undefined, result could (realistically) be either -1 or 1 struct t1 { int m; } s1; struct t2 { int m; } s2; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union { struct t1 s1; struct t2 s2; } u; u.s1.m = -1; return f(&u.s1,&u.s2); } 

GCC: -1, clang: -1, ICC: 1 e avvisa della violazione dell’aliasing

 // Global union declaration, result should be 1 according to a literal reading of 6.5.2.3/6 struct t1 { int m; } s1; struct t2 { int m; } s2; union u { struct t1 s1; struct t2 s2; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union uu; u.s1.m = -1; return f(&u.s1,&u.s2); } 

GCC: -1, clang: -1, ICC: 1 ma avverte della violazione di aliasing

 // Global union definition, result should be 1 as well. struct t1 { int m; } s1; struct t2 { int m; } s2; union u { struct t1 s1; struct t2 s2; } u; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { u.s1.m = -1; return f(&u.s1,&u.s2); } 

GCC: -1, clang: -1, ICC: 1, nessun avviso

Ovviamente, senza rigide ottimizzazioni di aliasing, tutti e tre i compilatori restituiscono ogni volta il risultato previsto. Dal momento che clang e gcc non hanno risultati distinti in nessuno dei casi, le uniche informazioni reali derivano dalla mancanza di una diagnosi dell’ICC sull’ultima. Ciò si allinea anche con l’esempio fornito dal comitato standard nel primo rapporto sui difetti sopra menzionato.

In altre parole, questo aspetto di C è un vero campo minato, e dovrai stare attento che il tuo compilatore stia facendo la cosa giusta anche se segui lo standard alla lettera. Tanto peggio dal momento che è intuitivo che una tale coppia di strutture debba essere compatibile in memoria.

Questo tipo di aliasing richiede specificamente un tipo di union . C11 §6.5.2.3 / 6:

Una garanzia speciale è fatta per semplificare l’uso dei sindacati: se un’unione contiene diverse strutture che condividono una sequenza iniziale comune (vedi sotto), e se l’object unione contiene attualmente una di queste strutture, è consentito ispezionare il comune parte iniziale di ognuno di essi dovunque sia visibile una dichiarazione del tipo completato dell’unione. Due strutture condividono una sequenza iniziale comune se i membri corrispondenti hanno tipi compatibili (e, per i campi bit, le stesse larghezze) per una sequenza di uno o più membri iniziali.

Questo esempio segue:

Quanto segue non è un frammento valido (perché il tipo di unione non è visibile all’interno della funzione f):

 struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union { struct t1 s1; struct t2 s2; } u; /* ... */ return f(&u.s1, &u.s2);} } 

I requisiti sembrano essere che 1. l’object alias è memorizzato all’interno di un union e 2. che la definizione di quel tipo di union è in ambito.

Per quel che vale, la corrispondente relazione iniziale-sottosuccessione in C ++ non richiede union . E in generale, tale dipendenza union sarebbe un comportamento estremamente patologico per un compilatore. Se c’è un modo in cui l’esistenza di un tipo di unione potrebbe influenzare un modello di memoria concerete, è probabilmente meglio non provare a immaginarlo.

Suppongo che l’intento sia che un verificatore di accesso alla memoria (pensa Valgrind su steroidi) possa controllare un potenziale errore di aliasing contro queste regole “severe”.