3

这种情况只能在没有名称修饰的情况下发生(我相信),所以下面的代码是 C。假设在 Ac 中有一个函数 A 定义为

void A(int x, int y){
    //Do stuff
}

现在还有一个单独的文件 Bc:

extern "C"{
    void A(int x, int y, int z);
}

void B(){
    A(1, 2, 3);
}

A 最初被声明为只有 2 个参数,但在 Bc 中声明时它有一个额外的参数,并且在 B() 中用第三个参数调用它。我知道有可能发生这种情况,例如在与 fortran 子例程链接时,或者在动态链接时。

我想将额外的参数传递给函数是不安全的,谁能解释当调用函数并将参数传递给它时内存中发生了什么?因此,传递这个既不使用也不想要的“额外”参数是多么安全。

额外的参数是否有可能覆盖函数中使用的内存空间?还是对 A 的函数调用为参数分配内存空间,然后告诉 A 参数内存块的开头在哪里,A 读出前两个参数并忽略最后一个参数,使其完全安全?

任何有关该功能的信息都会非常有启发性,谢谢。

4

7 回答 7

4

这取决于使用的调用约定。使用cdecl,调用者以从右到左的顺序将参数压入堆栈,然后被调用者通过偏移堆栈指针来访问它们。在这种情况下,调用太多参数不会破坏任何东西。

但是,如果您有一个从左到右的调用约定,那么事情就会中断。

于 2010-09-09T14:34:10.093 回答
4

联动是实现定义的,所以没有办法肯定地说。

也就是说,C 的其他特性(特别是 vardic 参数)强制实现通常允许它的实现。

例如,如果您编写以下代码,我不知道任何会失败的实现:

 printf("%d", 1, 2);

但是,它只会打印“1”。

这里的许多人都在提出cdecl和调用约定。但是,这些都不是标准的一部分,并且是某些实现的所有功能。这让我们回到我的第一句话。pascal__stdcall

于 2010-09-09T14:36:46.840 回答
3

使用cdecl 调用约定,调用者负责清理堆栈,因此这是安全的。相比之下,pascal调用约定让被调用者负责清理,所以这很危险。

于 2010-09-09T14:34:33.367 回答
1

在 C 中,这是违反约束的,因此会导致未定义的行为。

“如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。” (C99,§6.5.2.2)

也就是说,在实践中,它将主要取决于底层的调用约定。

于 2010-09-09T19:44:02.567 回答
0

这样的代码违反了单一定义规则(好吧,无论如何,C等价于它......)它是否有效完全是特定于平台的。

特别是在 x86 上,如果函数被声明__cdecl,那么它会工作,因为调用者清理堆栈,但如果是__stdcall(就像大多数 Win32 函数一样),被调用者清理堆栈,并且在这种情况下会错误地清理它(因为它有太多参数)。因此,这将取决于使用的外部函数的调用约定。

我不明白你为什么要这样做。

于 2010-09-09T14:32:40.353 回答
0

至少在 C 和 C++ 中它不会造成任何伤害。参数从右向左推送,被调用者负责堆栈清理。

但是,除非您使用可变参数或强制转换函数类型,否则编译器不会让您这样做。例如:

#include <stdio.h>

static void foo (int a, int b, int c, int d, int e, int f, int g)
{
    printf ("A:%d B:%d C:%d D:%d E:%d F:%d G:%d \n",
            a, b, c, d, e, f, g);
}

int main ()
{
    typedef void (*bad_foo) (int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int);
    foo (1, 2, 3, 4, 5, 6, 7);
    bad_foo f = (bad_foo) (&foo);
    f (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
}

如果您查看汇编代码,所有参数都被推入寄存器,但额外的一次只是被忽略。

于 2010-09-09T14:53:35.650 回答
-1

如果我做对了,这可能会导致您的程序从内存中执行随机代码。当一个函数被调用时,一些值,包括返回地址(函数完成后程序将跳转回的地址)被压入堆栈。之后,函数参数 (x, y, z) 被压入堆栈,程序跳转到函数的入口点。然后该函数将从堆栈中弹出参数(x,y),做一些事情,然后从堆栈中弹出返回地址(在这种情况下是z,这是错误的)并跳回它。

这是堆栈详细信息的一个很好的描述:http ://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html

于 2010-09-09T14:36:42.583 回答