15.5.3. Detrás del telón

Puede ser útil ver el código ensamblador que se genera con la llamada a una función virtual, para poder ver como funciona la ligadura dinámica. Aquí está la salida de un compilador a la llamada

i.adjust(1);

dentro de la función f(Instrument& i):

push 1
push si
mov  bx, word ptr [si]
call word ptr [bx+4]
add  sp, 4

Los argumentos de una llamada a una función C++, como los de a una función C, son colocados en la pila de derecha a izquierda (este orden es necesario para poder soportar las listas de argumentos variables de C), por lo que el argumento 1 se pone al principio en la pila. En este punto en la función, el registro si (que es parte de la arquitectura del procesador IntelX86™) contiene la dirección de i. También se introduce en la pila porque es la dirección de comienzo del objeto de interés. Hay que recordar que la dirección del comienzo del objeto corresponde al valor de this, y this es introducido en la pila de manera oculta antes de cualquier llamada a función, por lo que la función miembro sabe sobre qué objeto en concreto está trabajando. Debido a esto se verá siempre uno más que el número de argumentos introducidos en la pila antes de una llamada a una función miembro (excepto para las funciones miembro static, que no tienen this).

Ahora se puede ejecutar la llamada a la función virtual. Primero hay que producir el VPTR para poder encontrar la VTABLE. Para el compilador el VPTR se inserta al principio del objeto, por lo que el contenido de this corresponde al VPTR. La línea

mov bx, word ptr [si]

busca la dirección (word) a la que apunta si, que es el VPTR y la coloca dentro del registro bx.

El VPTR contenido en bx apunta a la dirección inicial de la VTABLE, pero el puntero de la función a llamar no se encuentra en la posición cero de la VTABLE, si no en la segunda posición (debido a que es la tercera función en la lista). Debido al modelo de memoria cada puntero a función ocupa dos bytes, por lo que el compilador suma cuatro al VPTR para calcular donde está la dirección de la función apropiada. Hay que hacer notar que este es un valor constante establecido en tiempo de compilación, por lo que lo único que ocurre es que el puntero a función que está en la posición dos apunta a adjust(). Afortunadamente, el compilador se encarga de todo y se asegura de que todos los punteros a funciones en todas las VTABLEs de una jerarquía particular se creen con el mismo orden, a pesar del orden en que se hayan sobreescrito las funciones en las clases derivadas.

Una vez se ha calculado en la VTABLE la dirección del puntero apropiado, se llama a la función a la que apunta el puntero. Esto es, se busca la dirección y se llama de una sola vez con la sentencia:

call word ptr [bx+4]

Finalmente, se retrocede el puntero de la pila para limpiar los argumentos que se pusieron antes de la llamada. En código ensamblador de C y de C++ se ve a menudo la instrucción para limpiar la lista de argumentos pero puede variar dependiendo del procesador o de la implementación del compilador.