调用C函数,该函数不带参数参数

关于C调用约定和64/32位编译之间可能存在未定义的行为,我有一些奇怪的问题。 首先是我的代码:

int f() { return 0; } int main() { int x = 42; return f(x); } 

正如你所看到的,我用参数调用f,而f不带参数。 我的第一个问题是这个论点在调用时是否真的给了f。

神秘的线条

经过一点点objdump我得到了好奇的结果。 传递x作为f的参数:

 00000000004004b6 : 4004b6: 55 push %rbp 4004b7: 48 89 e5 mov %rsp,%rbp 4004ba: b8 00 00 00 00 mov $0x0,%eax 4004bf: 5d pop %rbp 4004c0: c3 retq 00000000004004c1 : 4004c1: 55 push %rbp 4004c2: 48 89 e5 mov %rsp,%rbp 4004c5: 48 83 ec 10 sub $0x10,%rsp 4004c9: c7 45 fc 2a 00 00 00 movl $0x2a,-0x4(%rbp) 4004d0: 8b 45 fc mov -0x4(%rbp),%eax 4004d3: 89 c7 mov %eax,%edi 4004d5: b8 00 00 00 00 mov $0x0,%eax 4004da: e8 d7 ff ff ff callq 4004b6  4004df: c9 leaveq 4004e0: c3 retq 4004e1: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4004e8: 00 00 00 4004eb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 

不传递x作为参数:

 00000000004004b6 : 4004b6: 55 push %rbp 4004b7: 48 89 e5 mov %rsp,%rbp 4004ba: b8 00 00 00 00 mov $0x0,%eax 4004bf: 5d pop %rbp 4004c0: c3 retq 00000000004004c1 : 4004c1: 55 push %rbp 4004c2: 48 89 e5 mov %rsp,%rbp 4004c5: 48 83 ec 10 sub $0x10,%rsp 4004c9: c7 45 fc 2a 00 00 00 movl $0x2a,-0x4(%rbp) 4004d0: b8 00 00 00 00 mov $0x0,%eax 4004d5: e8 dc ff ff ff callq 4004b6  4004da: c9 leaveq 4004db: c3 retq 4004dc: 0f 1f 40 00 nopl 0x0(%rax) 

所以我们可以看到:

  4004d0: 8b 45 fc mov -0x4(%rbp),%eax 4004d3: 89 c7 mov %eax,%edi 

当我用x调用f但是因为我在汇编方面不是很好时,我真的不理解这些行。

64/32位矛盾

否则我尝试了其他的东西并开始打印我的程序堆栈。

用x给出的堆栈(以64位编译):

 Address of x: ffcf115c ffcf1128: 0 0 ffcf1130: -3206820 0 ffcf1138: -3206808 134513826 ffcf1140: 42 -3206820 ffcf1148: -145495616 134513915 ffcf1150: 1 -3206636 ffcf1158: -3206628 42 ffcf1160: -143903780 -3206784 

堆栈与x没有给f(编译为64位):

 Address of x: 3c19183c 3c191818: 0 0 3c191820: 1008277568 32766 3c191828: 4195766 0 3c191830: 1008277792 32766 3c191838: 0 42 3c191840: 4195776 0 

由于某种原因,在32位x似乎是推动堆栈。

堆栈与x给f(编译为32位):

 Address of x: ffdc8eac ffdc8e78: 0 0 ffdc8e80: -2322772 0 ffdc8e88: -2322760 134513826 ffdc8e90: 42 -2322772 ffdc8e98: -145086016 134513915 ffdc8ea0: 1 -2322588 ffdc8ea8: -2322580 42 ffdc8eb0: -143494180 -2322736 

为什么x会出现在32而不是64?

印刷代码: http : //paste.awesom.eu/yayg/QYw6&ln

我为什么要问这样愚蠢的问题?

  • 首先是因为我没有找到任何标准来回答我的问题
  • 其次,考虑在C中调用可变参数函数而不给出给定的参数计数。
  • 最后但同样重要的是,我认为未定义的行为很有趣。

感谢您抽出时间阅读,直到这里,帮助我理解某些内容或让我意识到我的问题毫无意义。

答案是,正如您所怀疑的,您正在做的是未定义的行为(在传递多余的参数的情况下)。

然而,许多实现中的实际行为是无害的。 在堆栈上准备参数,被调用函数忽略。 被调用的函数不负责从堆栈中删除参数,因此没有任何危害(例如不平衡的堆栈指针)。

这种无害的行为使得C黑客一度开发了一个变量参数列表工具,该工具曾经在Unix C库的古老版本中位于#include 之下。

这演变为ANSI C

这个想法是:将额外的参数传递给函数,然后动态地遍历堆栈以检索它们。

那今天不行。 例如,正如您所看到的,该参数实际上并未放入堆栈,而是加载到RDI寄存器中。 这是GCC在x86-64上使用的约定。 如果你在堆栈中游走,你将找不到前几个参数。 相比之下,在IA-32上,GCC使用堆栈传递参数:尽管您可以使用“fastcall”约定获得基于寄存器的行为。

va_arg宏将正确地考虑混合寄存器/堆栈参数传递约定。 (或者,相反,当您对可变参数函数使用正确的声明时,它可能会抑制寄存器中尾随参数的传递,因此va_arg 可以通过内存进行va_arg 。)

如果您添加了一些优化,那么您的机器代码可能更容易理解。 例如,序列

  4004c9: c7 45 fc 2a 00 00 00 movl $0x2a,-0x4(%rbp) 4004d0: 8b 45 fc mov -0x4(%rbp),%eax 4004d3: 89 c7 mov %eax,%edi 4004d5: b8 00 00 00 00 mov $0x0,%eax 

由于看起来像一些浪费的数据移动,它是相当迟钝的。

如何将参数传递给函数取决于平台ABI(应用程序二进制接口)。 ABI可以使用编译器X编译库,并将它们与使用编译器Y编译的代码一起使用。这些都不是由标准定义的。

标准没有要求“堆栈”甚至存在,更不用说它用于函数调用。

x86芯片的寄存器数量有限,ABI反映了这一事实; 正常的32位x86调用约定将堆栈用于所有参数。

64位架构不是这种情况,它有更多的寄存器,并使用其中一些寄存器用于前几个参数。 这显着加快了函数调用。

类似地,Windows 32位“fastcall”调用约定在寄存器中传递一些参数。 (为了使用非标准调用约定,您需要适当地注释函数声明,并在定义函数声明的位置一致。)

您可以在此Wikipedia文章中找到有关各种调用约定的更多信息。 AMD64 ABI可以在x86-64.org上找到(PDF文档) 。 最初的System V IA-32 ABI(Linux,xBSD和OS X上使用的ABI的基础)仍然可以从www.sco.com访问(PDF文档) 。


未定义的行为?

OP中提供的代码肯定是未定义的行为。

  1. 在函数定义中 ,空参数列表表示该函数不接受任何参数。 在函数声明中 ,空参数无法声明函数占用的参数数量。

    §6.7.6.3/ p.14:函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数 。 函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息

  2. 最终调用该函数时,必须使用正确数量的参数调用它:

    §6.5.2.2/ p.6:如果表示被调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将类型为float的参数提升为double … 如果参数的数量不等于参数的数量,行为是未定义的。

  3. 如果函数被定义为vararg函数(带有尾部省略号),则无论调用函数的哪个位置,vararg声明都必须是可见的。

    (继续上一个引用):如果函数是使用包含原型的类型定义的,并且原型以省略号(,…)结尾,或者促销后的参数类型与类型不兼容参数, 行为未定义。