dimensione di un sindacato in C / C ++

Qual è la dimensione del sindacato in C / C ++? È la dimensione del più grande datatype al suo interno? In tal caso, in che modo il compilatore calcola come spostare il puntatore dello stack se è attivo uno dei tipi di dati più piccoli dell’unione?

Lo standard risponde a tutte le domande nella sezione 9.5 dello standard C ++, o nella sezione 6.5.2.3, paragrafo 5 dello standard C99 (o paragrafo 6 dello standard C11):

In un sindacato, al massimo uno dei membri dei dati può essere attivo in qualsiasi momento, ovvero il valore di al massimo uno dei membri dei dati può essere memorizzato in un sindacato in qualsiasi momento. [Nota: viene creata una garanzia speciale per semplificare l’uso dei sindacati: Se un POD-union contiene diverse strutture POD che condividono una sequenza iniziale comune (9.2), e se un object di questo tipo POD-union contiene uno di le strutture POD, è consentito ispezionare la sequenza iniziale comune di uno qualsiasi dei membri di POD-struct; vedi 9.2. ] La dimensione di un sindacato è sufficiente per contenere il più grande dei suoi membri di dati. Ogni membro dei dati viene assegnato come se fosse l’unico membro di una struttura.

Ciò significa che ogni membro condivide la stessa area di memoria. C’è al massimo un membro attivo, ma non puoi scoprire quale. Dovrai memorizzare le informazioni sul membro attualmente attivo da qualche altra parte. Memorizzare una tale bandiera oltre al sindacato (per esempio avere una struct con un intero come type-flag e un union come data-store) ti darà una cosiddetta “unione discriminata”: un’unione che sa di che tipo è attualmente “attivo”.

Un uso comune è nei lexer, in cui è ansible avere diversi token, ma a seconda del token, si hanno informazioni diverse da memorizzare (mettendo una line in ogni struttura per mostrare che cos’è una sequenza iniziale comune):

 struct tokeni { int token; /* type tag */ union { struct { int line; } noVal; struct { int line; int val; } intVal; struct { int line; struct string val; } stringVal; } data; }; 

Lo standard ti consente di accedere alla line di ciascun membro, poiché questa è la sequenza iniziale comune di ciascun membro.

Esistono estensioni del compilatore che consentono l’accesso a tutti i membri ignorando quale attualmente ha il suo valore memorizzato. Ciò consente una reinterpretazione efficiente dei bit memorizzati con tipi diversi tra i membri. Ad esempio, è ansible utilizzare quanto segue per analizzare una variabile float in 2 cortometraggi senza segno:

 union float_cast { unsigned short s[2]; float f; }; 

Questo può essere abbastanza utile quando si scrive codice di basso livello. Se il compilatore non supporta quell’estensione, ma lo fai comunque, scrivi un codice i cui risultati non sono definiti. Quindi assicurati che il compilatore lo supporti se usi questo trucco.

Un union occupa sempre lo stesso spazio del membro più grande. Non importa ciò che è attualmente in uso.

 union { short x; int y; long long z; } 

Un’istanza della suddetta union richiederà sempre un long long di archiviazione.

Nota a margine : Come notato da Stefano , lo spazio effettivo che qualsiasi tipo ( union , struct , class ) assumerà dipenderà da altri problemi come l’allineamento del compilatore. Non l’ho passato per semplicità perché volevo solo dire che un sindacato prende in considerazione il più big oggetto. È importante sapere che le dimensioni effettive dipendono dall’allineamento .

Dipende dal compilatore e dalle opzioni.

 int main() { union { char all[13]; int foo; } record; printf("%d\n",sizeof(record.all)); printf("%d\n",sizeof(record.foo)); printf("%d\n",sizeof(record)); } 

Questo produce:

13 4 16

Se ricordo bene, dipende dall’allineamento che il compilatore inserisce nello spazio allocato. Quindi, a meno che tu non usi qualche opzione speciale, il compilatore inserirà il padding nello spazio dell’unione.

modifica: con gcc è necessario utilizzare una direttiva pragma

 int main() { #pragma pack(push, 1) union { char all[13]; int foo; } record; #pragma pack(pop) printf("%d\n",sizeof(record.all)); printf("%d\n",sizeof(record.foo)); printf("%d\n",sizeof(record)); } 

questa uscita

13 4 13

Puoi anche vederlo dallo sassembly (rimosso qualche printf, per chiarezza)

  0x00001fd2 : push %ebp | 0x00001fd2 : push %ebp 0x00001fd3 : mov %esp,%ebp | 0x00001fd3 : mov %esp,%ebp 0x00001fd5 : push %ebx | 0x00001fd5 : push %ebx 0x00001fd6 : sub $0x24,%esp | 0x00001fd6 : sub $0x24,%esp 0x00001fd9 : call 0x1fde  | 0x00001fd9 : call 0x1fde  0x00001fde : pop %ebx | 0x00001fde : pop %ebx 0x00001fdf : movl $0xd,0x4(%esp) | 0x00001fdf : movl $0x10,0x4(%esp) 0x00001fe7 : lea 0x1d(%ebx),%eax | 0x00001fe7 : lea 0x1d(%ebx),%eax 0x00001fed : mov %eax,(%esp) | 0x00001fed : mov %eax,(%esp) 0x00001ff0 : call 0x3005  | 0x00001ff0 : call 0x3005  0x00001ff5 : add $0x24,%esp | 0x00001ff5 : add $0x24,%esp 0x00001ff8 : pop %ebx | 0x00001ff8 : pop %ebx 0x00001ff9 : leave | 0x00001ff9 : leave 0x00001ffa : ret | 0x00001ffa : ret 

Dove l’unica differenza è nel main + 13, dove il compilatore alloca sullo stack 0xd invece di 0x10

Non esiste alcuna nozione di datatype attivo per un sindacato. Sei libero di leggere e scrivere qualsiasi “membro” dell’unione: spetta a te interpretare ciò che ottieni.

Pertanto, la dimensione di un’unione è sempre la dimensione del suo più grande datatype.

La dimensione sarà almeno quella del più grande tipo di composizione. Non esiste un concetto di tipo “attivo”.

Dovresti davvero considerare un unione come un contenitore per il più grande tipo di dati al suo interno combinato con una scorciatoia per un cast. Quando si utilizza uno dei membri più piccoli, lo spazio non utilizzato è ancora lì, ma rimane semplicemente inutilizzato.

Si vede spesso questo usato in combinazione con le chiamate ioctl () sotto Unix, tutte le chiamate ioctl () passeranno la stessa struttura, che contiene un’unione di tutte le possibili risposte. Ad esempio questo esempio proviene da /usr/include/linux/if.h e questa struct è usata in ioctl () per configurare / interrogare lo stato di un’interfaccia ethernet, i parametri di richiesta definiscono quale parte del sindacato è effettivamente in uso :

 struct ifreq { #define IFHWADDRLEN 6 union { char ifrn_name[IFNAMSIZ]; /* if name, eg "en0" */ } ifr_ifrn; union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; struct sockaddr ifru_netmask; struct sockaddr ifru_hwaddr; short ifru_flags; int ifru_ivalue; int ifru_mtu; struct ifmap ifru_map; char ifru_slave[IFNAMSIZ]; /* Just fits the size */ char ifru_newname[IFNAMSIZ]; void * ifru_data; struct if_settings ifru_settings; } ifr_ifru; }; 
  1. La dimensione del membro più grande.

  2. Questo è il motivo per cui i sindacati di solito hanno senso all’interno di una struttura che ha una bandiera che indica quale è il membro “attivo”.

Esempio:

 struct ONE_OF_MANY { enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag; union { short x; int y; long long z; }; }; 

Qual è la dimensione del sindacato in C / C ++? È la dimensione del più grande datatype al suo interno?

, la dimensione dell’unione è la dimensione del suo membro più grande.

Per esempio :

 #include union un { char c; int i; float f; double d; }; int main() { union un u1; printf("sizeof union u1 : %ld\n",sizeof(u1)); return 0; } 

Produzione :

 sizeof union u1 : 8 sizeof double d : 8 

Qui il più grande membro è il double . Entrambi hanno dimensione 8 . Quindi, come le hai detto correttamente, la dimensione dell’unione è davvero 8 .

in che modo il compilatore calcola come spostare il puntatore dello stack se è attivo uno dei tipi di dati più piccoli dell’unione?

Gestisce internamente dal compilatore. Supponiamo che stiamo accedendo a uno dei membri di unione dati, quindi non possiamo accedere ad altri membri dati poiché possiamo accedere a singoli membri di unione dati poiché ogni membro di dati condivide la stessa memoria. Usando Union possiamo salvare un sacco di spazio prezioso.