C语⾔的函数签名,关于CC++编译时的函数签名及连接过程今天⾯试的时候⾯试官花了不少时间琢磨⼀段 C 程序代码,因为之前⾃⼰学的时候喜欢看很多相关东西,⽽且确实看过有关编译器实现的部分细节所以基本也都答出来了,但确实被问到的时候⽽且在⾯试后⾃⼰试了⼀下才惊叹于这样居然也可以编译通过...
先说结论:C 函数签名只有函数名(我只记得 C++ 是函数名+参数类型了2333)
调⽤函数时参数进栈顺序是逆序(原因待考究)
相关知识点:函数调⽤原理,编译器对函数的签名,编译⼤体流程
涉及这个问题⾯试问答⼤体流程
考虑如下 C 代码:#include
#include
c语言编译器怎么用?int main()
{
printf("%d", strlen("hello"));
return 0;
}⾯试的时候⾸先让介绍了 #include 是什么(是预处理指令,在编译阶段进⾏处理,功能是引⼊相关⽂件代码,但并不是简单直接插⼊,因为要处理⼀些变量作⽤域等其他问题),下⾯⼤概是问答的过程
Q:如果将 #include 注释掉能否编译通过?
A:不能,因为⼀个函数要调⽤,必须要有相应的声明以及实现。
Q:如果将 string.h ⽂件⾥的内容写到 main 函数前能否编译通过?具体来说,插⼊⼀⾏ int strlen(char *);
A:可以编译,因为编译器已经到了 strlen 的声明知道该如何调⽤这个函数。
Q:但是这⾥只有声明没有具体代码,能正常执⾏吗?
A:可以,因为编译时是分单元编译的,main.o 的中间⽂件⽣成只需要这个⽂件中所有被调⽤的函数有声明即可;在连接阶段的时候编译器才会去寻其具体的实现代码,⽽ strlen 是 C 标准库提供的,连接阶段都会进⾏连接,所以相应的代码能够被正确连接并⽣成可执⾏⽂件。
Q:如果我把 strlen 的声明改为 int strlen(char *, int),并且在调⽤的时候给他传递第⼆个参数呢?能否编译通过?
A:应该不能,因为在连接阶段的时候不到对应的实现,我对 C++ ⽐较熟,C++ 编译器在⽣成函数签名的时候包括了返回值以及参数,连接的时候会⼀起检查,⽽ C 编译器我知道返回值是⼀定不包含在函数签名中的,⾄于参数有没有我不太确定。上⾯的回答潜台词就是:如果编译器认为函数签名⼀致则可以连接成功并⽣成可执⾏代码,在 C++ 中⼀定不能编译成功,⽽在 C 中取决于函数签名是否包含了参数。
上⾯的回答其实是有错误的点的,现在回忆⼀下 C++ 的函数签名也是没有返回值的,其只包括函数名及参数类型;⽽ C 语⾔则只有函数名,这也是 C 不⽀持函数重载的原因。也就是说上⾯问题的答案是可以编译通过。
应该是基于我上⾯的回答问了下⾯的问题:
Q:函数调⽤的过程是怎么样的?
A:⾸先 CPU 会保存当前上下⽂,然后将函数的参数⼊栈,然后 jmp 到函数的代码去执⾏,函数内部的局部变量的清理我记得似乎是可以分为两种,⼀种是调⽤者清理,⼀种是被调⽤者清理,似乎是看具体实现的标准来定的。在函数代码执⾏完毕之后会 ret 到原本的位置。
Q:参数进栈的顺序是怎么样的?
A:我感觉似乎顺序进栈和逆序进栈都可以,只要保证调⽤和被调⽤统⼀即可;但具体好像⼤部分都是实现的逆序进栈。
Q:如果上述的代码(传递两个参数的版本)确实能够调⽤到实际标准库中的代码,能够正确输出结果吗?
A:可以,因为进栈顺序是逆序的话,调⽤时进栈 2 次,第⼀个参数 char * 最后进栈,⽽ strlen ⾥⾯只 pop ⼀次取参数,取到的是正确的参数,因此可以正确执⾏代码。
结论
因为我⾃认为了解得相对⽐较透彻,前⾯的问题我都⽐较有把握,需要测试的时与标准库不同参数的声明能否正确编译运⾏;
⾯试完成之后我⽴刻新建了⼀个⽂件进⾏测试,测试结果是在 C 编译器下可以,⽽ C++ 不⾏,后者报错不到对应的实现,连接失败,因此可以得出以下结论:C 的函数签名只有函数名,不包括返回值也不包括参数
进栈顺序是逆序是为了让这种情况下也可以正确执⾏程序(保证参数组合的前缀相同即可?
⽽关于进栈顺序是逆序的进⼀步原因还有待查阅相关资料,私以为这类不匹配的情况就不应该让其能够编译运⾏,因为既然参数不匹配就说明程序员对参数记忆有误,⽽且有额外的参数是没有被使⽤到的;那么与其在测试的时候正确执⾏了,我认为还是直接在测试时崩溃查错更好,毕竟既然参数不匹配,就说明理解与编码存在偏差,⽽这个偏差就有可能代⼊产品中产⽣隐藏的 bug。
以上,欢迎交流,请多指教。