假设我有一个任意函数:
void someFunc(int, double, char);
我打电话给someFunc(8, 2.4, 'a');
,实际发生了什么?8、2.4 和 'a' 如何获取内存、移入该内存并传递给函数?对于这样的情况,编译器有哪些类型的优化?如果我混合和匹配参数,例如someFunc(myIntVar, 2.4, someChar);
?
如果函数声明为 会发生什么inline
?
假设我有一个任意函数:
void someFunc(int, double, char);
我打电话给someFunc(8, 2.4, 'a');
,实际发生了什么?8、2.4 和 'a' 如何获取内存、移入该内存并传递给函数?对于这样的情况,编译器有哪些类型的优化?如果我混合和匹配参数,例如someFunc(myIntVar, 2.4, someChar);
?
如果函数声明为 会发生什么inline
?
值是否是文字没有区别(除非函数是内联的,然后编译器可以优化一些东西)。
通常,参数被放入寄存器或函数参数堆栈。无论它们是显式值还是变量。
如果没有优化,参数将被推入参数堆栈。在第一种情况下,首先取值x
并放入寄存器eax
,然后压入参数堆栈。foo
打印x
。
foo(x);
00361A75 mov eax,dword ptr [x]
00361A78 push eax
00361A79 call get_4 (3612B7h)
00361A7E add esp,4
foo(3);
00361A81 push 3
00361A83 call get_4 (3612B7h)
00361A88 add esp,4
通过优化,编译器(在我的示例中)可以看到该函数,并且完全跳过了调用:
foo(x);
01011000 mov ecx,dword ptr [__imp_std::cout (101203Ch)]
01011006 push 3
01011008 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1012038h)]
foo(3);
0101100E mov ecx,dword ptr [__imp_std::cout (101203Ch)]
01011014 push 3
01011016 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1012038h)]
foo
定义为:
void foo(int x)
{
std::cout << x;
}
通常C++ 函数在堆栈上工作。我将描述C 调用约定,因为它无需优化即可工作:
它将放在'a'
堆栈的顶部。然后放到2.4
栈顶,再放到8
栈顶,然后为返回类型[1]添加空间,然后是调用函数中当前指令的指令指针,然后跳转到开头被调用函数的代码。
被调用的函数会查看栈,抓取参数,做它的事情,把结果放到栈上提供的空间,最后跳转到栈上指向的指令(大概就是那个函数的下一条指令)叫它)。现在处理器再次执行调用函数,然后它将结果复制到任何它想要的位置,并将所有其他内容从堆栈中弹出。*
还有其他调用约定(其中许多将参数保存在寄存器中)以节省时间,当然,由于异常,还有其他复杂性。根据调用函数的不同,它可能会内联调用,它会跳过列出的所有步骤,直接使用这些常量执行被调用的函数,或者在非常高级的编译器中,在某些条件下,它可能会创建函数的副本将这些参数硬编码,并调用该版本而不是无参数。
正如我所说,这是我多年前学到的所有理论。 Luchian Grigore 的答案有很多简洁的细节。
[1] Wikipedia 告诉我,如果结果是 single int
,它会在寄存器而不是堆栈中返回。