2010-11-17 16 views
7

mio codice è questoproblema concettuale a Union

// using_a_union.cpp 
#include <stdio.h> 

union NumericType 
{ 
    int   iValue; 
    long  lValue; 
    double  dValue; 
}; 

int main() 
{ 
    union NumericType Values = { 10 }; // iValue = 10 
    printf("%d\n", Values.iValue); 
    Values.dValue = 3.1416; 
    printf("%d\n", Values.iValue); // garbage value 
} 

Perché ottengo valore spazzatura quando provo a stampare Values.iValue dopo aver fatto Values.dValue = 3.1416? Ho pensato che il layout della memoria sarebbe stato come this. Cosa succede a Values.iValue e Values.lValue; quando assegno qualcosa a Values.dValue?

+1

si tratta di un problema di conversione endian? ~ Definisci "spazzatura". Questo ci aiuterebbe davvero a rispondere alla tua domanda. – jcolebrand

+0

@drachenstern: La sua domanda sembra perfettamente formulata e formattata ... molto meglio del solito "mostrami teh codez" che riceviamo dai nuovi arrivati. – mpen

+0

@Mark ~ Sono d'accordo. Ma dal momento che è nuovo, potrebbe essere bello avere un riferimento al sito. Inoltre, abbiamo bisogno di sapere cosa definisce "spazzatura" e sarebbe stata una buona inclusione. Almeno non l'ho collegato direttamente all'articolo di JonSkeet;) ... L'ho appena fatto per molti nuovi arrivati ​​come un po 'di pulizie. Benvenuto nel club, sii un buon membro della comunità, quel genere di cose. Non era inteso come post dannoso. – jcolebrand

risposta

8

In uno union, tutti i membri di dati si sovrappongono. Puoi utilizzare un solo membro dati di un sindacato alla volta.

iValue, lValue e dValue occupano tutti lo stesso spazio.

Non appena si scrive a dValue, le iValue e lValue membri non sono più utilizzabili: solo dValue è utilizzabile.


Edit: per affrontare il commenti qui sotto: Non è possibile scrivere a un membro di dati di un sindacato e poi leggere da un altro membro di dati. Per fare ciò si ha un comportamento indefinito. (C'è un'eccezione importante: puoi reinterpretare qualsiasi oggetto in C e C++ come una serie di char. Ci sono altre eccezioni minori, come la possibilità di reinterpretare un intero con segno come un numero intero senza segno.) Puoi trovare di più in entrambi i C Standard (C99 6.5/6-7) e C++ Standard (C++ 03 3.10, se ricordo correttamente).

Potrebbe questo "lavoro" in pratica un po 'di tempo? Sì. Ma a meno che il compilatore non dichiari espressamente che tale reinterpretazione è garantita per funzionare correttamente e specifica il comportamento che garantisce, non puoi fare affidamento su di essa.

+1

Non è vero (e non ti ho minimizzato).Gli altri due valori sono effettivamente utilizzabili. – jcolebrand

+0

@drachenstern: quale parte della mia risposta non è vera? Puoi utilizzare un solo membro dati di un sindacato alla volta. Ho frainteso la domanda? –

+1

Ancora, non è vero. unione Pixel {int rgb; char r; char g; char b; } Tutto 'utilizzabile'. –

7

Poiché floating point numbers are represented differently than integers are.

Tutte queste variabili occupano la stessa area di memoria (con il doppio che occupa più ovviamente). Se provi a leggere i primi quattro byte di quel doppio come un int, non hai intenzione di recuperare ciò che pensi. Hai a che fare con il layout della memoria grezza qui e hai bisogno di sapere come questi tipi sono rappresentati.


EDIT: Vorrei anche aggiunto (come James ha già fatto notare) che scrivere a una variabile in un'unione e quindi la lettura da un altro non invoca un comportamento indefinito e deve essere evitato (a meno che non si sono ri-interpretazione i dati come una matrice di caratteri).

0

Hai fatto questo:

union NumericType Values = { 10 }; // iValue = 10 
printf("%d\n", Values.iValue); 
Values.dValue = 3.1416; 

Come un compilatore utilizza la memoria per questa unione è simile ad usare la variabile con la più grande dimensione e l'allineamento (nessuno di loro se ci sono diversi), e reinterpretare getto quando uno degli altri tipi del sindacato è scritto/accesso, come in:

double dValue; // creates a variable with alignment & space 
       // as per "union Numerictype Values" 
*reinterpret_cast<int*>(&dValue) = 10; // separate step equiv. to = { 10 } 
printf("%d\n", *reinterpret_cast<int*>(dValue)); // print as int 
dValue = 3.1416;         // assign as double 
printf("%d\n", *reinterpret_cast<int*>(dValue)); // now print as int 

il problema è che nella creazione dValue a 3,1416 hai completamente sovrascritto i bit che utilizzate per contenere il numero 10. il nuovo valore potrebbe sembrare spazzatura, ma è semplicemente il risultato dell'interpretazione di t egli prima (sizeof int) byte del doppio 3.1416, credendo ci sia un valore int utile lì.

Se si desidera che le due cose siano indipendenti, quindi l'impostazione del doppio non influisce sull'int iniziale memorizzata, è necessario utilizzare struct/class.

Essa può aiutare a prendere in considerazione questo programma:

#include <iostream> 

void print_bits(std::ostream& os, const void* pv, size_t n) 
{ 
    for (int i = 0; i < n; ++i) 
    { 
     uint8_t byte = static_cast<const uint8_t*>(pv)[i]; 
     for (int j = 0; j < 8; ++j) 
      os << ((byte & (128 >> j)) ? '1' : '0'); 
     os << ' '; 
    } 
} 

union X 
{ 
    int i; 
    double d; 
}; 

int main() 
{ 
    X x = { 10 }; 
    print_bits(std::cout, &x, sizeof x); 
    std::cout << '\n'; 
    x.d = 3.1416; 
    print_bits(std::cout, &x, sizeof x); 
    std::cout << '\n'; 
} 

che, per me, ha prodotto questo output:

00001010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
10100111 11101000 01001000 00101110 11111111 00100001 00001001 01000000 

Fondamentalmente, la prima metà di ogni riga mostra i 32 bit che sono usato per iValue: nota il binario 1010 nel byte meno significativo (a sinistra su una CPU Intel come la mia) è 10 decimale. La scrittura 3.1416 modifica l'intero 64 bit in un modello che rappresenta 3.1416 (vedere http://en.wikipedia.org/wiki/Double_precision_floating-point_format). Il vecchio modello 1010 è sovrascritto, rovinato, non c'è più una memoria elettromagnetica.

2

Bene, osserviamo prima l'esempio più semplice. La risposta di Ed descrive la parte fluttuante, ma per quanto riguarda esaminiamo come vengono memorizzati ints e chars per primi!

Ecco un esempio che ho appena codificato up:

#include "stdafx.h" 
#include <iostream> 
using namespace std; 

union Color { 
    int value; 
    struct { 
     unsigned char R, G, B, A; 
    }; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Color c; 
    c.value = 0xFFCC0000; 
    cout << (int)c.R << ", " << (int)c.G << ", " << (int)c.B << ", " << (int)c.A << endl; 
    getchar(); 
    return 0; 
} 

Che cosa vi aspettate l'uscita di essere?

255, 204, 0, 0

Destra?

Se un int è 32 bit e ciascuno dei caratteri è 8 bit, allora R deve corrispondere al byte più a sinistra, G al secondo e così via.

Ma questo è sbagliato. Almeno sulla mia macchina/compilatore, sembra che gli interi siano memorizzati nell'ordine inverso dei byte. Ottengo,

0, 0, 204, 255

Quindi, per rendere questo dare l'uscita ci si aspetterebbe (o l'uscita mi sarei aspettato comunque), dobbiamo cambiare la struct a A,B,G,R. Questo ha a che fare con endianness.

In ogni caso, non sono un esperto in questa roba, solo qualcosa su cui mi sono imbattuto quando ho provato a decodificare alcuni binari. Il punto è che i float non sono necessariamente codificati nel modo in cui ti aspetteresti ... devi capire come sono archiviati internamente per capire perché stai ottenendo quell'output.

+0

+1 nice [8moretogo] –