将“太多”参数传递给外部函数是否安全?

时间:2010-09-09 14:29:39

标签: c linker fortran dynamic-linking

这种情况只能在没有名称错误的情况下发生(我相信),所以下面的代码是C. 假设在A.c中定义了一个函数A

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

现在还有一个单独的文件B.c:

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

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

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

我认为将一个额外的参数传递给一个函数是不安全的,任何人都可以在调用函数并将参数传递给它时解释内存中发生的事情吗?因此,传递既不使用也不想要的“额外”论证是多么安全。

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

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

7 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:4)

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

尽管如此,C的其他特性(特别是vardic参数)强制实现通常允许的实现。

例如,如果你写了:

,我不知道任何失败的实现
 printf("%d", 1, 2);
然而,它只会打印“1”。

这里的很多人都提出了cdeclpascal__stdcall来电约定。但是,这些都不是标准的一部分,而是某些实施的所有特征。这让我们回到了第一句话。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

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

  

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

尽管如此,实际上它主要取决于潜在的调用约定。

答案 4 :(得分:0)

这样的代码违反了One Definition Rule(无论如何,C等价于它......)它是否有效完全取决于平台。

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

我无法理解你为什么要这么做。

答案 5 :(得分: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);
}

如果查看汇编代码,所有参数都会被推送到寄存器中,但是会忽略额外的参数。

答案 6 :(得分:-1)

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

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