2010-09-09 69 views
3

这种情况只能发生没有名称重整(我相信),所以下面的代码是C. 说有在交流中定义的函数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读出前两个参数并忽略最后一个,使其完全安全?

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

+0

请注意,这里几乎所有的答案都假定为x86 - 在几个平台上,这可能永远不会有效。它完全取决于平台和调用约定。 – 2010-09-09 14:39:06

回答

4

联动是实现定义的,所以没有办法肯定地说。也就是说,C的其他功能(特别是vardic参数)强制执行通常允许的功能。

例如,我不知道,如果你写了会失败任何实现的:

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

它将,然而,仅仅是打印出“1”。

这里很多人都在调用cdecl,pascal__stdcall调用约定。然而,这些都不是标准的一部分,并且都是某些实施的特征。这将我们带回到第一句话。

+0

+1来补偿所有其他upvoted蹩脚的答案。 – 2010-09-09 16:56:58

+0

可变参数函数的存在实际上并不会强制编译器对非可变参数函数使用特定的调用约定。事实上,这恰恰是*为什么必须使用范围内的正确原型调用可变参数函数 - 以允许编译器使用特殊的调用约定。 – caf 2010-09-10 02:27:15

0

这样的代码违反了One Definition Rule(好吧,C等效于它......)无论它是否有效都完全是平台特定的。

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

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

+0

我认为你的意思是“如果它是'__stdcall'(就像大多数的win32函数一样),**被调用者**清除堆栈”。 – DrAl 2010-09-09 14:40:48

+0

用'__stdcall',你是指'被调用者'还是'被调用函数'?目前它再次说'呼叫者'。 – 2010-09-09 14:47:01

+0

@Al + @Jonathan:谢谢你们。固定。必须诅咒所有的大脑屁... – 2010-09-09 15:29:58

4

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

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

+3

实际上,这里的关键不仅仅是'cdecl'从左到右,还有'cdecl'意味着负责清理堆栈中的参数的函数是相同的推动他们。另一方面,'stdcall'以相同的顺序推入参数,但依靠被调用的函数来清理堆栈,这会破坏堆栈。 – torak 2010-09-09 14:37:53

+0

@torak:明白。但是如果有一个从左到右的调用约定(我想不出一个例子,但是),被调用者会看到无意义的论点。所以事情会以不同的方式打破。 – 2010-09-09 14:40:47

+0

有关x86调用约定的列表,请参阅http://en.wikipedia.org/wiki/X86_calling_conventions。 “帕斯卡”会议是从左向右推动的。 – torak 2010-09-09 15:07:04

3

随着cdeclcalling convention,调用者负责清理堆栈,所以这将是安全的。相比之下,pascal调用约定使被调用者负责清理,所以这会很危险。

+2

+1 - 注意'__stdcall'也需要被调用者清理堆栈。还要注意,调用约定完全是平台特定的,标准对它们完全没有提及。 – 2010-09-09 14:35:16

+0

-1根据实现细节给出答案,并忽略语言标准所说的内容。 – 2010-09-09 16:56:24

-1

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

这里的堆栈细节一个不错的描述:http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html

+0

这不是默认的('__cdecl')调用约定。 – 2010-09-09 14:37:34

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); 
} 

如果你看一下汇编代码,所有的参数都推到寄存器,但额外的onces才刚刚被忽略。

1

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

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

这就是说,实际上它将主要取决于潜在的调用约定。