2012-02-18 21 views
12

Mi stavo chiedendo, come vengono archiviati i riferimenti internamente? Sentivo che una comprensione in profondità in quel livello mi avrebbe fatto capire meglio il puntatore del concetto rispetto al riferimento e fare scelte decisionali.Come i riferimenti sono memorizzati internamente in C++?

Sospetto che funzioni fondamentalmente come puntatori ma il compilatore si occupa di gestire i puntatori. Si prega di avvisare.

+6

"Sentivo che una comprensione in profondità in quel livello mi avrebbe fatto capire meglio il concetto di puntatore vs riferimento" Non credo che sarebbe di alcun aiuto. –

+1

Puoi provare questo: http://eetimes.com/discussion/programming-pointers/4023307/References-vs-Pointers – Vlad

+0

@ MR.Anubis ..... cosa intendi? – howtechstuffworks

risposta

11

I riferimenti sono solo alias internamente il compilatore li considera come puntatori.

Ma per l'utente dal punto di vista dell'utilizzo ci sono alcune sottili differenze.

Alcune delle principali differenze sono:

  • I puntatori possono essere NULL mentre i riferimenti cannot.There c'è nulla chiamato come NULL di riferimento.
  • Un riferimento const estende la durata di un vincolo temporaneo ad esso. Non c'è equivalente con i puntatori.

Inoltre, i riferimenti hanno alcune cose in comune con const puntatori (non un puntatore a const):

  • Riferimenti deve essere inizializzati al momento della creazione.
  • Un riferimento è associato in modo permanente a un singolo percorso di archiviazione e non può essere successivamente rimbalzato.

Quando sai di avere qualcosa (un oggetto) a cui fare riferimento e non vorrai mai fare riferimento a nient'altro, usa un altro Riferimento.

+0

Non è esattamente la stessa cosa dei puntatori (non sempre). Il compilatore può cancellare completamente i riferimenti, ad esempio in 'class A {public: A(): a (_a) {} int const & a; private: int _a; }; ', il riferimento può essere eliminato (e quindi non può influenzare la dimensione dell'oggetto) mentre un puntatore * non può * essere elidato. –

+0

"mentre i riferimenti non possono" "int & a = * ((int *) 0);' – SigTerm

+5

@SigTerm Se miri con attenzione, puoi spararti ai piedi. –

14

Non è necessario che un riferimento sia "memorizzato" in alcun modo. Per quanto riguarda la lingua, un riferimento è solo un alias di qualche oggetto esistente, ed è tutto ciò che qualsiasi compilatore deve fornire.

È del tutto possibile che non sia necessario memorizzare nulla se il riferimento è solo una scorciatoia per qualche altro oggetto già in ambito o se una funzione con un argomento di riferimento viene evidenziata.

In situazioni in cui il riferimento deve essere manifestata (ad esempio quando si chiama una funzione in un'unità diversa traduzione), si può praticamente implementare un T & x come T * const e trattare ogni occorrenza di x come implicitamente dereferenziazione quello puntatore. Anche su un livello più alto si può pensare di T & x = y; e T * const p = &y; (e corrispondentemente di x e *p) come essenzialmente equivalente, quindi questo sarebbe un modo ovvio per implementare i riferimenti.

Ma ovviamente non c'è alcun obbligo e qualsiasi implementazione è libera di fare quello che vuole.

+0

Vedo, dal tuo post, penso che stai dicendo che per l'utilizzo del riferimento non è necessario allocare alcuna variabile extra, puoi semplicemente aggiungere il nome alla tabella delle variabili che stai mantenendo (è mia supposizione). Non so, come funziona C++ ora, ma se implemento un nuovo linguaggio oop, posso ancora assicurarmi che nessuna variabile extra sia allocata in memoria, ricorda solo che la variabile 'a' ha un altro nome chiamato "a_ref" ??? ? Ho capito bene? – howtechstuffworks

+1

@howtechstuffworks: purché il riferimento si riferisca solo a variabili di ambito locali o membri, allora sì. – Puppy

2

Siamo spiacenti per l'utilizzo di assemblaggio di spiegare questo, ma penso che questo sia il modo migliore per capire come i riferimenti sono implementate dai compilatori.

#include <iostream> 

using namespace std; 

int main() 
{ 
    int i = 10; 
    int *ptrToI = &i; 
    int &refToI = i; 

    cout << "i = " << i << "\n"; 
    cout << "&i = " << &i << "\n"; 

    cout << "ptrToI = " << ptrToI << "\n"; 
    cout << "*ptrToI = " << *ptrToI << "\n"; 
    cout << "&ptrToI = " << &ptrToI << "\n"; 

    cout << "refToNum = " << refToI << "\n"; 
    //cout << "*refToNum = " << *refToI << "\n"; 
    cout << "&refToNum = " << &refToI << "\n"; 

    return 0; 
} 

uscita di questo codice è come questo

i = 10 
&i = 0xbf9e52f8 
ptrToI = 0xbf9e52f8 
*ptrToI = 10 
&ptrToI = 0xbf9e52f4 
refToNum = 10 
&refToNum = 0xbf9e52f8 

Vediamo lo smontaggio (io ho usato GDB per questo. 8,9 e 10 qui sono i numeri di riga di codice)

8   int i = 10; 
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp) 

Qui $0xa è il 10 (decimale) che assegniamo a i. -0x10(%ebp) qui indica il contenuto di ebp register -16 (decimale). -0x10(%ebp) indica l'indirizzo di i sullo stack.

9   int *ptrToI = &i; 
0x0804869f <main()+25>: lea -0x10(%ebp),%eax 
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp) 

Assegnare indirizzo di i a ptrToI. ptrToI è di nuovo nello stack situato all'indirizzo -0x14(%ebp), ovvero ebp-20 (decimale).

10   int &refToI = i; 
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax 
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp) 

Ora ecco il problema! Confronta lo smontaggio delle linee 9 e 10 e osserverai che, -0x14(%ebp) è stato sostituito da -0xc(%ebp) nella riga numero -0xc(%ebp) è l'indirizzo di refNoNum. È assegnato in pila. Ma non sarai mai in grado di ottenere questo indirizzo dal tuo codice perché non ti è richiesto di conoscere l'indirizzo.

Così; un riferimento occupa memoria. In questo caso è la memoria dello stack poiché l'abbiamo allocata come variabile locale. Quanta memoria occupa? Quanto un puntatore occupa.

Ora vediamo come accediamo al riferimento e ai puntatori. Per semplicità ho mostrato solo una parte del frammento di assemblaggio

16   cout << "*ptrToI = " << *ptrToI << "\n"; 
0x08048746 <main()+192>:  mov -0x14(%ebp),%eax 
0x08048749 <main()+195>:  mov (%eax),%ebx 
19   cout << "refToNum = " << refToI << "\n"; 
0x080487b0 <main()+298>:  mov -0xc(%ebp),%eax 
0x080487b3 <main()+301>:  mov (%eax),%ebx 

Ora confrontare queste due linee, si vedrà sorprendente somiglianza. -0xc(%ebp) è l'indirizzo effettivo di refToI che non è mai accessibile a te. In termini semplici, se si pensa al riferimento come puntatore normale, accedere a un riferimento è come recuperare il valore all'indirizzo indicato dal riferimento. Il che significa che le sotto due righe di codice vi darà lo stesso risultato

cout << "Value if i = " << *ptrToI << "\n"; 
cout << " Value if i = " << refToI << "\n"; 

Ora confrontare questo

15   cout << "ptrToI = " << ptrToI << "\n"; 
0x08048713 <main()+141>:  mov -0x14(%ebp),%ebx 
21   cout << "&refToNum = " << &refToI << "\n"; 
0x080487fb <main()+373>:  mov -0xc(%ebp),%eax 

Credo che si è in grado di individuare ciò che sta accadendo qui. Se si richiede &refToI, il contenuto della posizione indirizzo -0xc(%ebp) viene restituito e -0xc(%ebp) è dove risiede refToi e il suo contenuto non è altro che l'indirizzo di i.

Un'ultima cosa, perché questa linea è stata commentata?

//cout << "*refToNum = " << *refToI << "\n"; 

Perché *refToI non è permesso e vi darà un errore di tempo di compilazione.

Problemi correlati