2012-12-21 19 views
17

Possiamo osservare la rappresentazione di un oggetto di tipo T convertendo un T* che punta a quell'oggetto in un char*. Almeno nella pratica:Quando e come è consentita la conversione in puntatore char?

int x = 511; 
unsigned char* cp = (unsigned char*)&x; 
std::cout << std::hex << std::setfill('0'); 
for (int i = 0; i < sizeof(int); i++) { 
    std::cout << std::setw(2) << (int)cp[i] << ' '; 
} 

Emette la rappresentazione di 511 sul mio sistema: ff 01 00 00.

C'è (sicuramente) un comportamento definito dall'implementazione che si verifica qui. Quale dei cast mi consente di convertire un int* in un unsigned char* e quali conversioni comporta tale cast? Sto invocando un comportamento indefinito non appena lancio? Posso trasmettere qualsiasi tipo di T* in questo modo? Su cosa posso contare quando lo faccio?

+3

non credo che sia un comportamento indefinito, almeno se non modifichi i dati.Ma il risultato dipenderà dal fatto che la tua piattaforma sia piccola o grande endian. – Synxis

+2

Si noti che questo è sicuro solo per 'char *'. Puntatori del cast per farli leggere come tipi diversi causa problemi con * aliasing *. I linguaggi C e C++ garantiscono al compilatore che puntatori a tipi diversi non possono mai puntare allo stesso oggetto in modo che l'ottimizzatore possa fare cose come memorizzare il valore in un registro o caricare un carico o scrivere un ciclo. 'char *' è l'unica eccezione. Un 'char *' deve essere assunto come alias con qualsiasi cosa, a causa della serializzazione da e verso i buffer del disco e della rete. –

+2

@ZanLynx - Re "' char * 'è l'unica eccezione": non del tutto. Lo standard consente anche la conversione in 'char senza segno *'. –

risposta

12

Quali dei cast mi consente di convertire un int* in un unsigned char*?

Il cast di tipo C in questo caso è lo stesso di reinterpret_cast<unsigned char*>.

Posso trasmettere qualsiasi tipo di T * come questo?

Sì e no. La parte sì: è possibile trasmettere in modo sicuro qualsiasi tipo di puntatore a char* o unsigned char* (con i qualificatori const e/o volatile appropriati). Il risultato è definito dall'implementazione, ma è legale.

Nessuna parte: lo standard consente esplicitamente char* e unsigned char* come tipo di destinazione. Tuttavia, non è possibile (ad esempio) trasmettere in modo sicuro un double* a un int*. Fai questo e hai superato il limite dal comportamento definito dall'implementazione al comportamento non definito. Viola la rigida regola dell'aliasing.

+1

Aha, quindi sembra (dalle risposte di @ GeneBushuyev e @ nobar) il cast da 'T *' a qualsiasi 'U *' ha un risultato non specificato (ma sarebbe bene se ricomincio di nuovo) e se dovessi eseguire il cast su qualsiasi cosa tranne un 'char *' o 'unsigned char *' e quindi * accedere * all'oggetto tramite quel puntatore, avrei un comportamento indefinito (come per il rigoroso aliasing). La risposta perfetta avrebbe entrambi questi punti. ;) –

2

Il comportamento di implementazione nel tuo esempio è l'attributo endianness del tuo sistema, in questo caso la tua CPU è un po 'endian.
Informazioni sul tipo di casting, quando lanci un int* su char* tutto quello che stai facendo è dire al compilatore di interpretare ciò che cp punta come un char, quindi leggerà solo il primo byte e lo interpreterà come un carattere.

5

Le mappe del cast a:

unsigned char* cp = reinterpret_cast<unsigned char*>(&x); 

La rappresentazione sottostante di un int è implementazione definita, e la visualizzazione come caratteri consente di esaminare questo. Nel tuo caso, è little endian a 32 bit.

Non c'è niente di speciale qui - questo metodo di esaminare la rappresentazione interna è valido per qualsiasi tipo di dati.

C++ 03 5.2.10.7: Un puntatore a un oggetto può essere convertito in modo esplicito in un puntatore a un oggetto di tipo diverso. Tranne che la conversione di un valore di rvalore di tipo "puntatore a T1" al tipo "puntatore a T2" (dove T1 e T2 sono tipi di oggetti e dove i requisiti di allineamento di T2 non sono più rigidi di quelli di T1) e torna al suo tipo originale il valore del puntatore originale, il risultato di tale conversione del puntatore non è specificato.

Ciò suggerisce che i risultati del cast in comportamento non specificato. Ma parlando in modo pragmatico, la trasmissione da qualsiasi tipo di puntatore a char* consentirà sempre di esaminare (e modificare) la rappresentazione interna dell'oggetto di riferimento.

+0

Strettamente parlando, però, lo standard non garantisce che un 'char' sia più piccolo di un' int'. – nobar

+2

Gli standard pertinenti per la "regola di aliasing rigoroso" sono forniti qui: http://stackoverflow.com/a/7005988/86967. Sinossi: se accedi all'oggetto tramite 'char *' o 'unsigned char *', non ci sono problemi. – nobar

+0

Questo è altamente tangenziale, ma è interessante notare che la regola di aliasing_strict_ suggerisce che l'uso di un 'char *' può interferire con l'ottimizzazione. Qui è dove la parola chiave "non-standard" (http://stackoverflow.com/questions/6434549/does-c11-add-the-c99-restrict-specifier-if-not-why-not) 'restrict' può essere utile - sebbene non si applichi alla domanda in questione, poiché _aliasing_ è esattamente il punto della domanda data. – nobar

1

Il cast tra i puntatori è sempre possibile poiché tutti i puntatori non sono altro che gli indirizzi di memoria e qualsiasi tipo, in memoria, può sempre essere pensato come una sequenza di byte.

Ma, naturalmente, il modo in cui viene creata la sequenza dipende dal modo in cui il tipo scomposto viene rappresentato in memoria, e questo è fuori dallo scopo delle specifiche C++.

Detto questo, a meno che non si tratti di casi molto patologici, ci si può aspettare che la rappresentazione sia la stessa su tutto il codice prodotto da uno stesso compilatore per tutte le macchine di una stessa piattaforma (o famiglia), e non bisogna aspettarsi lo stesso risultati su diverse piattaforme.

In generale, una cosa da evitare è esprimere la relazione tra le dimensioni dei caratteri come "predefinite": nell'esempio si assume sizeof(int) == 4*sizeof(char): non è necessariamente sempre vero.

Ma è sempre vero che sizeof (T) = N * sizeof (char), quindi tutto ciò che T può sempre essere visto come un numero intero di char-s

+0

Mi manca dove OP assunse che 'sizeof (int) == 4 * sizeof (char)'. – phonetagger

+0

@phonetagger Emilio potrebbe aver risposto alla versione originale della mia domanda che si basava su "4 * sizeof (char)". –

+0

@sftrabbit - Fine. Ma cancella il tuo commento dicendo "Non c'è bisogno di cancellare ..." – phonetagger

0

A meno che non si dispone di un operatore di cast, poi un il cast sta semplicemente dicendo di "vedere" quell'area di memoria in un modo diverso. Niente di veramente stravagante, direi.

Quindi, si legge l'area di memoria byte per byte; finché non lo cambi, va bene. Ovviamente, il risultato di ciò che vedi dipende molto dalla piattaforma: pensa a endianness, word size, padding e così via.

0

Basta invertire l'ordine dei byte allora diventa

00 00 01 ff 

Quale è 256 (01) + 255 (FF) = 511

Questo perché il platfom è little endian.

3

Il cast in stile C in questo caso equivale a reinterpret_cast. Lo standard descrive la semantica in 5.2.10. Nello specifico, al punto 7:

"Un puntatore a un oggetto può essere esplicitamente convertito in un puntatore a una diversa type.70 oggetto Quando un prvalue v di tipo‘puntatore a T1’è convertito nel tipo “puntatore a CVT2”, il risultato è static_cast<cvT2*>(static_cast<cvvoid*>(v)) se entrambi T1 e T2 sono tipi standard di layout (3.9) ed i requisiti di allineamento di T2 sono più severe di quelle di T1. Conversione di un prvalue di tipo “puntatore a T1 "Al tipo" puntatore a T2 "(dove T1 e T2 sono tipi di oggetti e dove i requisiti di allineamento di T2 non sono più rigidi di quelli di T1) e al suo tipo originale restituisce il valore del puntatore originale. Il risultato di qualsiasi altro tale conversione puntatore non è specificato."

Che cosa significa nel tuo caso, i requisiti di allineamento sono soddisfatti, e il risultato non è specificato.

+0

Ah, quindi è solo ben definito quando si lancia da un 'T *' a un 'U *' e si torna a un 'T *'? Il risultato di un cast di 'T *' su 'U * 'non è specificato? Aha. –