正如其他人指出的那样,行为是未定义的(所以任何事情都可能发生)。
让我们看一下三种“典型”行为。传递参数的三种常见方式是:
Intel x86 系统大多使用第一种方法(但有时使用第二种或第三种方法)。基于 MIPS 的处理器大多使用第二种。
如果一个系统使用一个或多个堆栈,通常的调用方法是:
- 在调用者(一些操作系统提供的调用 的例程
main
)中,推送参数,通常是从右到左,即以相反的顺序。堆栈推送通常(但不总是)看起来像*--sp = value;
在 C 中,堆栈指针从某个高地址下降。
- 调用目标函数 (
main
)
- 在目标函数中,从“堆栈”或“参数堆栈”或“当前线程堆栈”或系统使用的任何内容中检索参数。因为它们是按相反的顺序推送的,所以它们位于 , 等地址
sp[0]
。sp[1]
如果调用机制使用与参数传递机制相同的堆栈,则索引可能从 1 或 2 甚至更多(sp[2]
作为第一个参数,对于例如,并且sp[3]
是第二个)。
在这种情况下,argc
可能会出现正确的结果,但argv
会误解调用者推送的任何内容,从而产生看起来很奇怪的int
. 如果底层系统足够花哨(检查类型),它可能会检测到调用者推送了一个 type 值,char **
但您正在访问 type 之一int
,并给您某种运行时错误。大多数简单的系统更喜欢尽快给你错误的答案,但跳过类型检查。所以你会得到一个看起来很奇怪的int
,但它实际上是基于(至少部分——见下文)调用者试图传递的实际指针值。
如果系统使用通用寄存器(而不是或之前使用堆栈——如果您使用许多参数,使用 GPR 的系统通常会退回到堆栈,并且有时将它们用于所有可变参数函数,即那些使用<stdarg.h>
工具的函数),那么调用方法看起来更像这样:
- 在调用者中,将参数(
int argc
值和char **argv
值)移动到前两个参数寄存器中(例如,%o0
在%o1
SPARC 上,或$a0
在$a1
MIPS 上)。
- 调用目标函数
- 在目标函数中,访问参数寄存器中的值
在这种情况下,代码的行为通常与基于堆栈的系统上的行为相同。它只是运行得更快,因为寄存器中的参数往往比内存中的参数需要更少的 CPU 周期。(这就是为什么一些英特尔编译器有时会在寄存器中传递一个或两个参数。)
但是,如果系统使用特殊用途的寄存器,我们会得到一个新的明显行为。假设浮点值进入f
寄存器(在某些 SPARC 系统上为 true;x86 具有 MMX 和 SSE 寄存器);指针值进入a
寄存器(la 680x0 CPU);和整数值进入d
寄存器(680x0,虽然实际上大多数 680x0 系统只使用“堆栈”,但假设我们有一个使用寄存器的系统)。这一次,调用的东西main
需要传递一个整数,argc
和一个指针argv
,所以它这样做:
- 将整数参数
argc
移入数据寄存器d0
- 将指针参数移动
argv
到指针寄存器a0
- 称呼
main
现在,在 中main()
,您告诉编译器期待两个整数参数,它们将分别到达寄存器d0
和d1
。CPU寄存器中有d1
什么?谁知道,被调用的东西main
并没有在调用之前设置它。它有它所具有的任何价值,无论是谁最后在其中插入了一些价值。该值不再与预期相关联argv
,因为它在寄存器中a0
。
现在,即使您有一个基于堆栈或 GPR 的调用系统,还有一些问题需要考虑:
- 如果指针是 64 位而 plain
int
s 只有 32 位呢?在这种情况下,调用者压入一个 64 位值,或者将一个 64 位值写入参数寄存器;但main
只看 32 位。你会看到实际给出的一半。
- 如果指针是 32 位而普通
int
s 是 64 位怎么办?可以肯定的是,这是一个不寻常的实现,但现在您将查看一个仅提供 32 的值的所有 64 位。“额外”的 32 位可能全为零(这对于 GPR 中的参数来说是典型的),或者可能是某个不相关值的 32 位,类似于调用者填写registerd1
时检查 register 的情况。main
a0
- 当然,没有什么说 32 位和 64 位是唯一可能的大小。在 IBM AS/400 系统上,指针长达 128 位(16 字节标记的指针),并且有大量的运行时类型检查。这些机器致力于确保代码正确,而不仅仅是快速。
还有另一种值得注意的可能性。如果您构建类似的 C++ 代码(使用 以外的函数main
),它通常无法链接。原因是 C++ 编译器经常使用一种称为“名称修改”的技术来处理重载函数。一个f
接受一个int
和一个char **
参数并返回int
的函数会产生链接时符号Z1fiPPC
。一个名为的函数f
接受两个int
s 并返回,而是int
生成链接时符号。Z1fii
我还没有看到 C 编译器可以做到这一点,但他们可以做到。在这种情况下,编译器会在链接时检查您的程序是否定义了Z4mainippC
——<code>int main(int, char **)——如果是,链接到提供这些参数的调用者;或者它会检查Z4mainv
——<code>int main(void)——在这种情况下,调用者中的链接不提供任何参数。如果两个函数都没有找到,链接器可能会检测到您编写了不正确的代码main
,并且根本不会生成可执行文件!