如果在调用DLL中的函数时没有传递足够的参数,会发生什么?

Fra*_*ank 15 c c++ dll

在dll项目中,函数是这样的:

extern "C" __declspec(dllexport) void foo(const wchar_t* a, const wchar_t* b, const wchar_t* c)
Run Code Online (Sandbox Code Playgroud)

在另一个项目中,我将使用foo函数,但我foo在头文件中声明函数

extern "C" __declspec(dllimport) void foo(const wchar_t* a, const wchar_t* b)
Run Code Online (Sandbox Code Playgroud)

我只用两个参数调用它.

结果是成功,我认为这是关于__cdecl电话,但我想知道这是如何以及为什么这样做.

Jas*_*n C 32

32位

默认调用约定是__cdecl,这意味着调用者从右到左将参数压入堆栈,然后在调用返回后清理堆栈.

所以在你的情况下,来电者:

  1. 推b
  2. 推动一个
  3. 按下返回地址
  4. 调用该函数.

此时堆栈看起来像这样(例如假设4个字节的指针,并且记住堆栈指针在你推动时向后移动):

+-----+ <--- 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]
+-----+
Run Code Online (Sandbox Code Playgroud)

太好了.现在问题发生在被叫方身上.被调用者期望参数位于堆栈上的某些位置,因此:

  • a 假设是在 [esp+4]
  • b 假设是在 [esp+8]
  • c 假设是在 [esp+12]

这就是问题所在:我们不知道到底是什么[esp+12].因此,调用者将看到正确的价值观ab,但会解释一切未知的垃圾恰好是在[esp+12]c.

那时它几乎是未定义的,取决于你的功能实际上做了什么c.

在所有这些结束并且被调用者返回之后,假设您的程序没有崩溃,调用者将恢复esp并且堆栈指针将返回它应该的位置.所以从调用者的POV来看,一切都很好,堆栈指针最终回到原来的位置,但是被调用者看到了垃圾c.


64位

64位计算机上的机制不同,但最终结果大致相同.Microsoft 在64位计算机上使用以下调用约定,无论其__cdecl什么等等(您指定的任何约定都被忽略,并且所有约定都被视为相同):

  • 放置在寄存器前四个整数或指针参数rcx,rdx,r8,和r9以该顺序,左到右.
  • 放置在寄存器的前四个浮点参数xmm0,xmm1,xmm2,和xmm3,顺序,由左到右.
  • 剩下的任何东西都会从右到左被推到堆栈中.
  • 调用者负责恢复esp以及在调用后恢复所有易失性寄存器的值.

所以在你的情况下,来电者:

  1. 提出arcx.
  2. 提出brdx.
  3. 在堆栈上分配额外的32个字节的"阴影空间"(参见MS文章).
  4. 按下返回地址.
  5. 调用该函数.

但被调用者期待:

  • a假设在rcx(检查!)
  • b假设在rdx(检查!)
  • c假设在r8(问题)

因此,与32位的情况下,无论发生被叫方解释是在r8c和潜在hijinks随之而来,与根据与被叫做什么最终效果c.当它返回时,假设程序没有崩溃,调用者恢复所有易失性寄存器(rcx以及rdx通常包括r8和朋友)并恢复esp.

  • 请注意[对于64位二进制文​​件](https://msdn.microsoft.com/en-us/library/ms235286.aspx),参数将在寄存器中传递.除此之外,效果与此处描述的几乎完全相同 - 未知垃圾只是从寄存器而不是堆栈存储器中读取. (6认同)