2009-04-11 21 views
34

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?sizeof a union in C/C++

risposta

25

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

In un'unione, al massimo uno dei membri di dati può essere attivo in qualsiasi momento, ovvero il valore di al massimo uno dei membri di 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 oggetto 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 è possibile 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 è in lexer, dove si può avere diversi gettoni, ma a seconda del modo, si hanno diverse informazioni per memorizzare (mettendo line in ogni struttura per mostrare ciò che 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 consente di accedere a 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 il valore attualmente memorizzato. Ciò consente una reinterpretazione efficiente dei bit memorizzati con tipi diversi tra i membri. Ad esempio, possono essere utilizzati per sezionare una variabile float in 2 pantaloncini firmati:

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

che può venire molto 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.

+3

Un orribile esempio di male standard di linguaggio IMHO - in effetti l'intera sezione sui sindacati sembra un po 'trascurata. Perché introdurre il concetto di "attivo"? –

+2

GCC supporta almeno in modo esplicito la lettura incrociata dei membri del sindacato. e se i membri sono in qualche modo collegati in base alla 3.10/15 o sono compatibili con il layout, sono abbastanza sicuro che puoi ancora leggere l'altro membro anche se non è quello "attivo". –

+0

È il bit "attivo" che mi dà. se 9.5 \ 1 dovessero iniziare con "il valore di al massimo" allora non ci sarebbe bisogno di introdurre questo nebuloso concetto di "attivo". Ma questo dovrebbe (se dovunque) essere su comp.lang.C++. Std, e non in una stupenda casella di commento SO! Quindi firmo su questo argomento. –

50

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

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

Un esempio di quanto sopra union sarà sempre prendere almeno un long long per la conservazione.

Nota a margine: Come notato da Stefano, lo spazio effettivo qualsiasi tipo (union, struct, class) assumerà non dipendono da altri problemi come l'allineamento dal compilatore. Non l'ho passato per semplicità perché volevo solo dire che un sindacato prende in considerazione il più grande oggetto. È importante sapere che la dimensione effettiva corrisponde a dipende dall'allineamento.

+0

Un luogo in cui sizeof potrebbe restituire qualcosa di più grande quando viene utilizzato un doppio lungo. Un doppio lungo è di 10 byte ma Intel consiglia di allineare a 16 byte. – dreamlax

+0

long double is ... beh, dipende dal compilatore. I * think * I compilatori PowerPC usano 128bit double double –

+0

Sì whoops volevo dire un lungo double su x86. – dreamlax

10

Non esiste alcuna nozione di tipo di dati attivo per un'unione. 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 tipo di dati.

+2

Ovviamente si sbaglia ... la lingua dello standard si riferisce esplicitamente al tipo di dati attivo. Tuttavia, sizeof è un'operazione in fase di compilazione e, quindi, non dipende dal tipo di dati attivo. –

+1

@JimBalter - Sei corretto sullo standard. Quello che voglio dire è che in C non puoi interrogare un'unione sul suo tipo _attivo _. Nulla impedisce al programmatore di scrivere un float e di leggere un int (e ottenere spazzatura). – mouviciel

+2

Hai detto "Non esiste una nozione di datatype attivo per un sindacato". Ti sbagliavi; proprio. Non servirà a sostenere che intendevi qualcosa di molto diverso da ciò che hai scritto solo per cercare di evitare di aver sbagliato. "Nulla impedisce al programmatore di scrivere un float e leggere un int (e ottenere spazzatura)." - Ovviamente nulla lo impedisce ... lo Standard C non * previene * nulla; ti dice solo se quel comportamento è definito - non lo è. Come è stato notato più volte, UB include qualsiasi cosa, persino la detonazione di armi nucleari. Per alcune persone, questo impedisce loro di codificare UB. –

3

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

+0

Tranne che sì, c'è. –

2

Si dovrebbe davvero considerare un unione come un contenitore per il tipo di dati più grande 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() in Unix, tutte le chiamate ioctl() passeranno la stessa struttura, che contiene un'unione di tutte le possibili risposte. Per 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, e.g. "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; 
}; 
0
  1. la dimensione del più grande membro.

  2. Ecco perché 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; }; 
}; 
+1

Non vero. Un uso comune è accedere a parti più piccole di un tipo più grande. Esempio: union U {int i; char c [4]; }; può essere utilizzato per fornire (implementazione specifica) l'accesso ai byte di un intero a 4 byte. –

+0

Oh, vero ... non ho notato questa possibilità. Ho sempre avuto accesso a parti di un tipo più grande usando shift di byte e quel tipo di cose. – pyon

+0

@anon - implementazione specifica o semplicemente UB, a seconda del compilatore. Affidarsi anche al primo è una cattiva pratica se può essere evitata. –

15

Dipende dal compilatore, e sulle 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)); 

} 

This uscite:

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

edit: 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

si può vedere anche dal smontare (rimosso alcune printf, per chiarezza)

0x00001fd2 <main+0>: push %ebp    | 0x00001fd2 <main+0>: push %ebp 
    0x00001fd3 <main+1>: mov %esp,%ebp  | 0x00001fd3 <main+1>: mov %esp,%ebp 
    0x00001fd5 <main+3>: push %ebx    | 0x00001fd5 <main+3>: push %ebx 
    0x00001fd6 <main+4>: sub $0x24,%esp  | 0x00001fd6 <main+4>: sub $0x24,%esp 
    0x00001fd9 <main+7>: call 0x1fde <main+12> | 0x00001fd9 <main+7>: call 0x1fde <main+12> 
    0x00001fde <main+12>: pop %ebx    | 0x00001fde <main+12>: pop %ebx 
    0x00001fdf <main+13>: movl $0xd,0x4(%esp) | 0x00001fdf <main+13>: movl $0x10,0x4(%esp)           
    0x00001fe7 <main+21>: lea 0x1d(%ebx),%eax | 0x00001fe7 <main+21>: lea 0x1d(%ebx),%eax 
    0x00001fed <main+27>: mov %eax,(%esp)  | 0x00001fed <main+27>: mov %eax,(%esp) 
    0x00001ff0 <main+30>: call 0x3005 <printf> | 0x00001ff0 <main+30>: call 0x3005 <printf> 
    0x00001ff5 <main+35>: add $0x24,%esp  | 0x00001ff5 <main+35>: add $0x24,%esp 
    0x00001ff8 <main+38>: pop %ebx    | 0x00001ff8 <main+38>: pop %ebx 
    0x00001ff9 <main+39>: leave     | 0x00001ff9 <main+39>: leave 
    0x00001ffa <main+40>: ret      | 0x00001ffa <main+40>: ret  

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

+0

Grazie - mi salva dover assemblare questa risposta! –

+3

Sì, suppongo che avremmo dovuto tutti dire "A _least_ grande quanto il tipo più grande contenuto". –

+1

@Neil: l'allineamento del compilatore è un problema completamente diverso. Succede anche nelle strutture e dipende anche dal luogo in cui si inserisce l'unione nella struttura. Mentre questo è certamente vero, penso che complichi solo la risposta a * questa * domanda.btw, stavo attento ad allineare il mio campione di unione a 8byte boundary :-p –

0

Qual è la dimensione dell'unione in C/C++? La dimensione del più grande tipo di dati al suo interno?

, La dimensione del sindacato è la dimensione del suo più grande membro.

Per esempio:

#include<stdio.h> 

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; 
} 

uscita:

sizeof union u1 : 8 
sizeof double d : 8 

Ecco il più grande membro è double. Entrambi hanno la dimensione 8. Quindi, come correttamente detto sizeof, la dimensione dell'unione è effettivamente 8.

in che modo il compilatore calcola come spostare il puntatore dello stack se uno del tipo di dati più piccolo dell'unione è attivo?

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.