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
Intel™ X86™)
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.