2010-10-17 17 views
20

Simplemente me pregunto cómo se implementa realmente en diferentes compiladores y configuraciones de depuración/liberación. ¿El estándar de alguna manera proporciona recomendaciones sobre su implementación? ¿Difiere en cualquier lugar?¿Cómo se implementa la referencia internamente?

Traté de ejecutar un programa simple en el que he estado devolviendo referencias y punteros no const a las variables locales de las funciones, pero funcionó de la misma manera. Entonces, ¿es cierto que la referencia interna es solo un puntero?

+2

Totalmente definido por la implementación. El estándar indica si una referencia utiliza almacenamiento o no está sin especificar. Probablemente, un puntero es más fácil, pero muchas veces las referencias se pueden eliminar por completo. – GManNickG

Respuesta

12

La aplicación natural de referencia es de hecho un puntero. Sin embargo, no dependas de esto en tu código.

+0

Por supuesto, no hay forma de que esto suceda. – Keynslug

+0

@Martin York, se podría depender de sizeof (T *) == sizeof (T &) –

+5

@Peter G: Eso no funcionará como esperaba. Como las referencias son alias, el RHS en realidad está obteniendo el tamaño de T (como T y es un alias de un T). Por lo tanto, solo se cumple cuando sizeof (T) == sizeof (void *). Intente imprimir el 'sizeof (char &)' devolverá 1. Los alias no introducen una nueva variable, sino que introducen un nuevo nombre para una variable (por lo que es posible que ni siquiera necesiten que se implementen punteros (si la variable y la referencia están en el mismo alcance) –

1

que no se puede decir que esto es correcto a ciencia cierta, pero lo hice algunas google y encontramos este comunicado:

El lenguaje estándar no requiere ningún mecanismo particular . Cada implementación de es gratuita en cualquier forma , siempre que el comportamiento sea compatible con .

Fuente: Bytes.com

36

Sólo para repetir algunas de las cosas que todo el mundo ha estado diciendo, veamos algunos salida del compilador:

#include <stdio.h> 
#include <stdlib.h> 

int byref(int & foo) 
{ 
    printf("%d\n", foo); 
} 
int byptr(int * foo) 
{ 
    printf("%d\n", *foo); 
} 

int main(int argc, char **argv) { 
    int aFoo = 5; 
    byref(aFoo); 
    byptr(&aFoo); 
} 

podemos compilar esto con LLVM (con optimizaciones apagados) y obtenemos el siguiente:

define i32 @_Z5byrefRi(i32* %foo) { 
entry: 
    %foo_addr = alloca i32*       ; <i32**> [#uses=2] 
    %retval = alloca i32       ; <i32*> [#uses=1] 
    %"alloca point" = bitcast i32 0 to i32   ; <i32> [#uses=0] 
    store i32* %foo, i32** %foo_addr 
    %0 = load i32** %foo_addr, align 8    ; <i32*> [#uses=1] 
    %1 = load i32* %0, align 4      ; <i32> [#uses=1] 
    %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0] 
    br label %return 

return:           ; preds = %entry 
    %retval1 = load i32* %retval     ; <i32> [#uses=1] 
    ret i32 %retval1 
} 

define i32 @_Z5byptrPi(i32* %foo) { 
entry: 
    %foo_addr = alloca i32*       ; <i32**> [#uses=2] 
    %retval = alloca i32       ; <i32*> [#uses=1] 
    %"alloca point" = bitcast i32 0 to i32   ; <i32> [#uses=0] 
    store i32* %foo, i32** %foo_addr 
    %0 = load i32** %foo_addr, align 8    ; <i32*> [#uses=1] 
    %1 = load i32* %0, align 4      ; <i32> [#uses=1] 
    %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0] 
    br label %return 

return:           ; preds = %entry 
    %retval1 = load i32* %retval     ; <i32> [#uses=1] 
    ret i32 %retval1 
} 

Los cuerpos de las dos funciones son idénticas

0

referencia no es puntero. Esto es un hecho. El puntero se puede unir a otro objeto, tiene sus propias operaciones, como desreferencia e incremento/decremento.

Aunque internamente, la referencia puede implementarse como un puntero. Pero este es un detalle de implementación que no cambia el hecho de que las referencias no pueden intercambiarse con punteros. Y uno no puede escribir código asumiendo que las referencias se implementan como punteros.

9

Perdón por usar el 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 reemplaza 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 apuntada por 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 refToi reside 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.

1

No hay necesidad de una referencia para ser un puntero. En muchos casos, lo es, pero en otros casos es solo un alias y no hay necesidad de una asignación de memoria separada para un puntero. las muestras de ensamblaje no siempre son correctas, ya que dependen en gran medida de las optimizaciones y de cuán "inteligente" es el compilador.

por ejemplo: int i; int & j = i;

no necesita generar ningún código adicional ni asignar ninguna memoria adicional.

1

En palabras de Bjarne:

Al igual que un puntero, una referenciaes un alias para un objeto, es generalmente aplicado a celebrar una dirección de máquina de un objeto, y no impone una sobrecarga de rendimiento en comparación con los punteros , pero difiere de un puntero en que:

• Accede a una referencia con exactamente la misma sintaxis que el nombre de un objeto.

• Una referencia siempre se refiere al objeto para el que se inicializó.

• No hay '' referencia nula '', y podemos suponer que una referencia se refiere a un objeto


Aunque un referencia es en realidad un puntero, pero no se debe utilizar como un puntero sino como un alias.

Cuestiones relacionadas