我想知道使用内联函数时堆栈会发生什么。 如果从main函数调用一个简单的内联函数,那么在堆栈中推送的东西的序列是什么。以及在正常函数的情况下它将是怎样的。
另一个问题是内联如何影响已编译代码在内存中的归属位置。
答案 0 :(得分:1)
堆栈的使用在很大程度上取决于您正在编译的CPU。使用INTEL 32位处理器,你有这么少的寄存器,它经常需要将变量放在堆栈上。在64位处理器上,他们增加了8个寄存器,使得堆栈的使用不那么突出。在MIPS处理器上,您有32个寄存器,并且通常可以使用这些寄存器完成所有操作,而无需依赖堆栈。在Cray系统(使用旧的矢量化处理器)上,您有64个64个寄存器的文件,如果您需要进行一些矩阵计算...在诸如NVidia等GPU上,您可能有16个寄存器x个线程可能是例如1,500,所以24,000个寄存器。
因此,在不知道您所针对的架构,使用的编译器的情况下,回答您的问题并不是一门精确的科学。那就是说......
inline
可能会也可能不会对堆栈产生影响,具体取决于编译器是否对内联代码感到满意,以及函数本身是否需要使用堆栈进行计算。
实际上,在C ++中,您可能会使用几乎所有模板的标准C ++库。这意味着,所有代码在使用时都会内联。因此,例如,当您编写代码来检索向量的大小时:
my_vector.size();
内联。在大多数情况下,这意味着INTEL系统上的处理器,类似这样的东西(AT& T表示法):
mov (%eax), %ebx
现在,如果您要查看矢量实现,您的第一反应应该是真的害怕。然而,编译器做了很多魔术并且可以完全优化它们(主要成本是编译所有这些所需的时间和内存......在这方面,内存明智的cl是有限的,Microsoft C ++编译器。)
现在,如果你得到你的向量的大小来调用另一个不会被内联的函数,就像这样:
complicated_function(my_vector.size());
然后结果是这样的,因为需要将大小发送到被调用的函数:
mov (%eax), %ebx
push %ebx ; end up stacking the size() from the inlined call
call complicated_function ; this pushes the return PC on the stack
add $4, %esp
再次注意,在某些系统上,它们在其调用约定中使用寄存器,因此您可能不会使用堆栈。例如,在MIPS处理器上,您根本不需要堆栈:
ld $r3, ($r9) ; get vector size (inlined)
jal complicated_function ; call another sub-function
(不完全确定MIPS寄存器的一般约定,但代码看起来与我在这里显示的非常类似。)
而在complex_function()中,$ al甚至可能不需要保存在堆栈中。 (因为jal不会将当前的$ pc推送到堆栈上,而是将其保存在$ al寄存器中!)
对于问题的最后一部分:内联时代码是否已经过去了?显然,它是你调用函数的地方,用编译的内联函数的指令替换它,假设它被内联,当然。
另一个有趣的行为,如果你的函数返回一个常量值(即在编译时已知的值),那么编译器可能会更加优化你的代码。即使你的函数有一个非常复杂的表达式。您可能有兴趣查看constexpr
关键字,我认为当您可以使用它时,我认为该关键字优于inline
关键字:
http://en.cppreference.com/w/cpp/language/constexpr
最后,既然你在询问内联,你可能会对always_inline属性感兴趣,那就是g ++的特定属性,但它会让你感兴趣:
答案 1 :(得分:0)
我假设您正在讨论内联函数,而不仅仅是内联链接。根据定义,如果编译器内联函数,则在调用站点没有显式函数调用,因为编译器已将该调用替换为函数体。因此,呼叫没有堆栈设置。但是,在此函数的原始主体中声明和使用的局部变量也在内联代码中保留局部变量 - 它们将像任何其他自动持续时间变量一样在堆栈上分配。
关于问题的第二部分 - "内联如何影响已编译代码在内存中的归属位置" - 如果内联所有对此函数的调用,则链接器甚至不需要包含此函数的定义。但是,如果调用它不可内联,则此函数将具有与任何其他函数类似的定义。
答案 2 :(得分:0)
内联不是编译器的义务,而是请求。编译器也可以是内联方法,这些方法不符合" inline"关键词。它取决于编译器优化标志。如果编译器决定内联,那么所有内联调用都被函数体替换,但如果编译器决定不内联,它的行为就像任何其他方法一样。尝试使用具有优化ON和OFF的反汇编符号调试程序,以便自己查看差异。
答案 3 :(得分:0)
你可以合理地建立一个"电话"内联函数类似于宏扩展,例如:
inline double f(double a, double b, double c) { return a * b + 2 / (c + b); }
// caller...
double x = 10;
double y = f(x, 2.3 * x, -9.0);
...通常类似于......
double x = 10;
double _b = 2.3 * x;
double y = x * _b + 2 / (-9.0 + _b);
除了" _b
"不是您稍后可以引用的命名变量。请注意,我明确列出_b
,以突出显示2.3 * x
将为" b
"" #define F(A, B, C) ((A) * (B) + 2 / (C + B))
// double y = F(x, 2.3 * x, -9.0);
double y = ((x) * (2.3 * x) + 2 / (-9.0 + (2.3 * x)));
"参数。这是 与宏扩展不同 ,如下所示,它被评估两次:
2.3 * x
如果对程序状态,I / O等的副作用没有可观察到的差异,则语言允许编译器自由使用不同的代码,因此它可以考虑按照{{1}}重复计算宏观,但我的直觉是,它们在功能上并不相同 - 我想在那里有小的舍入差异,可能有不同的溢出阈值,但我并没有对细节感到满意。
所以,这不是"堆栈中推送内容的顺序"对于内联电话 - 你基本上可以忽略它的任何方式"呼叫"并且只考虑代码,就好像步骤直接在调用代码中一样。
另一个问题是内联如何影响已编译代码在内存中的归属位置。
同样,生成的代码就好像没有调用一样,因此调试器可能会报告执行步进通过"调用者"没有提到内联函数。调试器可以随心所欲地执行任何操作,因此在某些系统上,编译器可以以某种方式注释调试符号数据,以便编译器在其认为合适的时刻提及内联函数。