valores una retornan referencia que por paso parametros parametro llamar funciones funcion ejercicios con como c++ c dll

una - parametros por referencia c++



Si no paso suficientes parámetros cuando llamo a una función en una DLL, ¿qué pasará? (1)

En un proyecto dll, la función es así:

extern "C" __declspec(dllexport) void foo(const wchar_t* a, const wchar_t* b, const wchar_t* c)

En un proyecto diferente, foo función foo , pero declaro la función foo en el archivo de encabezado con

extern "C" __declspec(dllimport) void foo(const wchar_t* a, const wchar_t* b)

y lo llamo con solo dos parámetros.

El resultado es un éxito, creo que se trata de __cdecl llamada __cdecl , pero me gustaría saber cómo y por qué funciona.


32 bits

La convención de llamadas predeterminada es __cdecl , lo que significa que la persona que llama empuja los parámetros a la pila de derecha a izquierda y luego limpia la pila después de que la llamada retorna.

Entonces en tu caso, la persona que llama:

  1. Empuja b
  2. Empuja a
  3. Empuja la dirección de retorno
  4. Llama a la función.

En este punto, la pila se ve así (supongamos indicadores de 4 bytes, por ejemplo, y recuerda que el puntero de la pila viaja hacia atrás cuando empujas cosas):

+-----+ <--- this is where esp is after pushing stuff | ret | [esp] +-----+ | a | [esp+4] +-----+ | b | [esp+8] +-----+ <--- this is where esp was before we started | ??? | [esp+12 and beyond] +-----+

Vale genial. Ahora el problema ocurre en el lado de callee. El destinatario espera que los parámetros estén en ciertas ubicaciones en la pila, entonces:

  • a se supone que está en [esp+4]
  • b se supone que está en [esp+8]
  • c se supone que está en [esp+12]

Y aquí es donde está el problema: no tenemos idea de qué hay en [esp+12] . Entonces el destinatario de la llamada verá los valores correctos de b , pero interpretará cualquier basura desconocida que esté en [esp+12] como c .

En ese punto, es bastante indefinido, y depende de lo que tu función realmente haga con c .

Después de que todo esto termine y el destinatario vuelva, suponiendo que el programa no se bloqueó, el llamador restaurará esp y el puntero de pila volverá donde debería estar. Por lo tanto, desde el Punto de vista de la persona que llama todo está bien y el puntero de la pila termina donde debería estar, pero el destinatario ve basura por c .

64 bits

La mecánica en las máquinas de 64 bits es diferente, pero el resultado final es más o menos el mismo efecto. Microsoft utiliza la siguiente convención de llamadas en máquinas de 64 bits, independientemente de __cdecl o lo que sea (cualquier convención que especifique se ignorará y todas se tratarán de manera idéntica):

  • Los primeros cuatro argumentos enteros o de puntero colocados en los registros rcx , rdx , r8 y r9 , en ese orden, de izquierda a derecha.
  • Los primeros cuatro argumentos de coma flotante colocados en los registros xmm0 , xmm1 , xmm2 y xmm3 , en ese orden, de izquierda a derecha.
  • Todo lo que queda se empuja a la pila, de derecha a izquierda.
  • La persona que llama es responsable de restaurar esp y restaurar los valores de todos los registros volátiles después de la llamada.

Entonces en tu caso, la persona que llama:

  1. Pone a en rcx .
  2. Pone b en rdx .
  3. Asigna 32 bytes adicionales de "espacio sombra" en la pila (ver ese artículo MS).
  4. Empuja la dirección de retorno.
  5. Llama a la función.

Pero el llamado está esperando:

  • a supone que está en rcxrcx !)
  • b supone que está en rdxrdx !)
  • c supone que está en r8 (problema)

Y así, al igual que en el caso de 32 bits, el destinatario interpreta lo que sucedió que está en r8 como c , y se producen posibles "hijinks", con el efecto final dependiendo de lo que el destinatario haga con c . Cuando vuelve, suponiendo que el programa no falla, la persona que llama restaura todos los registros volátiles ( rcx y rdx , y también generalmente incluye r8 y amigos) y restaura esp .