2012-02-18 13 views
12

Me preguntaba, ¿cómo se almacenan internamente las referencias? Sentí que una comprensión profunda de ese nivel me hará entender mejor el puntero del concepto frente a la referencia y tomar decisiones.¿Cómo se almacenan internamente las referencias en C++?

Sospecho que básicamente funciona igual que los punteros, pero el compilador se encarga de manejar los punteros. Por favor avise.

+6

"Sentí que un entendimiento en el fondo de ese nivel me haría entender mejor el concepto de puntero contra referencia" No creo que eso ayude. –

+1

Puedes probar esto: http://eetimes.com/discussion/programming-pointers/4023307/References-vs-Pointers – Vlad

+0

@ MR.Anubis ..... ¿qué quieres decir? – howtechstuffworks

Respuesta

11

Las referencias son solo alias internos que el compilador los trata igual que los punteros.

Pero para el usuario desde la perspectiva de uso hay varias diferencias sutiles.

Algunas de las principales diferencias son:

  • punteros se pueden NULL mientras que las referencias cannot.There hay nada llamado como NULL referencia.
  • Una referencia const amplía la vida útil de un límite temporal. No hay equivalente con punteros.

Además, las referencias tienen algunas cosas en común con const punteros (no es un puntero a const):

  • Referencias debe ser inicializado en el momento de la creación.
  • Una referencia está vinculada permanentemente a una única ubicación de almacenamiento y no puede recuperarse posteriormente.

Cuando sabe que tiene algo (un objeto) al que hacer referencia y nunca querrá referirse a nada más, utilice una referencia else use punteros.

+0

No es exactamente lo mismo que los punteros (no siempre). El compilador puede eludir referencias por completo, por ejemplo en 'clase A {public: A(): a (_a) {} int const & a; private: int _a; }; ', la referencia puede eliminarse (y, por lo tanto, no puede influir en el tamaño del objeto) mientras que un puntero * no puede * ser eliminado. –

+0

"mientras que las referencias no pueden" 'int & a = * ((int *) 0); ' – SigTerm

+5

@SigTerm Si apunta con cuidado, puede dispararse en el pie. –

14

No es necesario que una referencia sea "almacenada" de ninguna manera. En lo que respecta al lenguaje, una referencia es solo un alias de algún objeto existente, y eso es todo lo que cualquier compilador debe proporcionar.

Es muy posible que no haya necesidad de almacenar nada en absoluto si la referencia es solo abreviada para algún otro objeto que ya esté dentro del alcance, o si una función con un argumento de referencia se inserta.

En situaciones en las que hay se manifestará con la referencia (por ejemplo, cuando se llama a una función en una unidad de traducción diferente), que prácticamente se puede implementar un T & x como T * const y tratar a cada ocurrencia de x eliminación de referencias como implícitamente que el puntero. Incluso en un nivel superior, puede pensar en T & x = y; y T * const p = &y; (y correspondientemente en x y *p) como esencialmente equivalentes, por lo que esta sería una forma obvia de implementar referencias.

Pero, por supuesto, no hay ningún requisito, y cualquier implementación es libre de hacer lo que quiera.

+0

Veo, de su publicación, creo que está diciendo, que para usar referencia, no hay necesidad de asignar ninguna variable adicional, solo puede agregar el nombre a la tabla de variables que está manteniendo (aunque es mi suposición). No sé cómo funciona C++ ahora, pero si implemento un nuevo lenguaje oop, aún puedo asegurarme de que no se asigne ninguna variable extra en la memoria, solo recuerde que la variable 'a' tiene otro nombre llamado "a_ref" ??? ? ¿Lo entendí bien? – howtechstuffworks

+1

@howtechstuffworks: siempre que la referencia solo se refiera a variables de ámbito local o miembro, entonces sí. – Puppy

2

Disculpe el uso del ensamblado para explicar esto, pero creo que esta es la mejor manera de entender cómo los compiladores implementan las referencias.

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

salida de este código es como esto

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

Veamos el desmontaje (he usado para este BGF. 8,9 y 10 aquí están los números de línea de código)

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

Aquí $0xa es el 10 (decimal) que estamos asignando a i. -0x10(%ebp) aquí significa contenido de ebp register -16 (decimal). -0x10(%ebp) apunta a la dirección de i en la pila.

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

Asignar dirección de i a ptrToI. ptrToI está de nuevo en la pila ubicada en la dirección -0x14(%ebp), que es ebp - 20 (decimal).

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

Ahora aquí está el truco! Compare el desmontaje de la línea 9 y 10 y observará que -0x14(%ebp) se reemplazó por -0xc(%ebp) en la línea número 10. -0xc(%ebp) es la dirección de refToNum. Se asigna en la pila. Pero nunca podrá obtener esta dirección de su código porque no es necesario que conozca la dirección.

So; una referencia ocupa memoria. En este caso, es la memoria de la pila ya que la hemos asignado como una variable local. ¿Cuánta memoria ocupa? Tanto un puntero ocupa.

Ahora veamos cómo accedemos a la referencia y los punteros. Por simplicidad he mostrado sólo una parte del fragmento de ensamblaje

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 

Ahora compare las dos líneas anteriores, verá sorprendente similitud. -0xc(%ebp) es la dirección real de refToI a la que nunca puede acceder. En términos simples, si considera que la referencia es un puntero normal, acceder a una referencia es como obtener el valor en la dirección a la que apunta la referencia. Lo que significa que los siguientes dos líneas de código le dará el mismo resultado

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

Ahora comparar este

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

supongo que son capaces de detectar lo que está sucediendo aquí. Si solicita &refToI, se devuelve el contenido de la dirección -0xc(%ebp) y -0xc(%ebp) donde reside refToi y su contenido no es más que la dirección de i.

Una última cosa, ¿Por qué se comenta esta línea?

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

Debido *refToI no está permitida y se le dará un error de tiempo de compilación.

Cuestiones relacionadas