这听起来太过一个简单的问题,在某个地方已经没有回答,但我试着环顾四周,我找不到任何简单的答案。请看以下示例:
class vec
{
double x;
double y;
};
inline void sum_x(vec & result, vec & a, vec & b)
{
result.x = a.x + b.x;
}
inline void sum(vec & result, vec & a, vec & b)
{
sum_x(result, a, b);
result.y = a.y + b.y;
}
当我致电sum
并编译时会发生什么?将sum
和sum_x
都内联,以便它只是转换为内联汇编代码来对两个组件求和吗?
这看起来像一个简单的例子,但我正在使用一个具有模板中定义的维度的向量类,因此迭代对向量的操作看起来有点像这样。
答案 0 :(得分:2)
inline
只是对编译器的一个提示。编译器是否实际内联函数是一个不同的问题。对于gcc,有一个始终内联属性来强制执行此操作。
__attribute__((always_inline));
总是内联你应该实现你所描述的(代码生成就像在一个函数中编写的那样)。
但是,通过编译器应用的所有优化和转换,您只能确定是否检查生成的代码(程序集)
答案 1 :(得分:1)
是的,可以递归方式应用内联。
您在此处执行的所有操作都可以在呼叫站点内联。
请注意,这与您使用inline
关键字几乎没有关系,除了它对ODR的影响(这可能非常明显)只是一个提示,现在大多数都被忽略了实际内联。这些函数将被内联,因为你聪明的编译器可以看到它们是它的好选择。
您实际可以判断它是否正在执行此操作的唯一方法是自行检查生成的程序集。
答案 2 :(得分:0)
这取决于。 inline
只是对编译器的暗示,它可能想要考虑内联该函数。编译器完全可以内联两个调用,但这取决于实现。
作为一个例子,这里有一些来自GCC的美化组装输出,有或没有这个简单程序的inline
:
int main()
{
vec a;
vec b;
std::cin >> a.x;
std::cin >> a.y;
sum(b,a,a);
std::cout << b.x << b.y;
return 0;
}
内联:
main:
subq $40, %rsp
leaq 16(%rsp), %rsi
movl std::cin, %edi
call std::basic_istream<char, std::char_traits<char> >& std::basic_istream<char, std::char_traits<char> >::_M_extract<double>(double&)
leaq 24(%rsp), %rsi
movl std::cin, %edi
call std::basic_istream<char, std::char_traits<char> >& std::basic_istream<char, std::char_traits<char> >::_M_extract<double>(double&)
movsd 24(%rsp), %xmm0
movapd %xmm0, %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, 8(%rsp)
movsd 16(%rsp), %xmm0
addsd %xmm0, %xmm0
movl std::cout, %edi
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movsd 8(%rsp), %xmm0
movq %rax, %rdi
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movl $0, %eax
addq $40, %rsp
ret
subq $8, %rsp
movl std::__ioinit, %edi
call std::ios_base::Init::Init()
movl $__dso_handle, %edx
movl std::__ioinit, %esi
movl std::ios_base::Init::~Init(), %edi
call __cxa_atexit
addq $8, %rsp
ret
不
sum_x(vec&, vec&, vec&):
movsd (%rsi), %xmm0
addsd (%rdx), %xmm0
movsd %xmm0, (%rdi)
ret
sum(vec&, vec&, vec&):
movsd (%rsi), %xmm0
addsd (%rdx), %xmm0
movsd %xmm0, (%rdi)
movsd 8(%rsi), %xmm0
addsd 8(%rdx), %xmm0
movsd %xmm0, 8(%rdi)
ret
main:
pushq %rbx
subq $48, %rsp
leaq 32(%rsp), %rsi
movl std::cin, %edi
call std::basic_istream<char, std::char_traits<char> >& std::basic_istream<char, std::char_traits<char> >::_M_extract<double>(double&)
leaq 40(%rsp), %rsi
movl std::cin, %edi
call std::basic_istream<char, std::char_traits<char> >& std::basic_istream<char, std::char_traits<char> >::_M_extract<double>(double&)
leaq 32(%rsp), %rdx
movq %rdx, %rsi
leaq 16(%rsp), %rdi
call sum(vec&, vec&, vec&)
movq 24(%rsp), %rbx
movsd 16(%rsp), %xmm0
movl std::cout, %edi
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movq %rbx, 8(%rsp)
movsd 8(%rsp), %xmm0
movq %rax, %rdi
call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
movl $0, %eax
addq $48, %rsp
popq %rbx
ret
subq $8, %rsp
movl std::__ioinit, %edi
call std::ios_base::Init::Init()
movl $__dso_handle, %edx
movl std::__ioinit, %esi
movl std::ios_base::Init::~Init(), %edi
call __cxa_atexit
addq $8, %rsp
ret
如你所见,GCC在被要求时会内联这两个功能。
如果您的程序集有点生疏,只需注意sum
存在并在第二个版本中调用,但不在第一个版本中调用。
答案 3 :(得分:0)
如上所述,inline
关键字只是一个提示。但是,编译器在这里做了一个了不起的工作(即使没有你的提示),并且它们以递归方式内联。
如果您真的对这些内容感兴趣,我建议您学习一下编译器设计。我最近一直在研究它,它让我想起了我们今天生产质量编译器的复杂动物。
关于内联,这是编译器倾向于做得非常好的事情之一。这是必要的,因为如果你看看我们如何用C ++编写代码,我们经常编写访问器函数(方法),只是为了返回单个变量的值。 C ++的受欢迎程度在很大程度上取决于我们可以利用信息隐藏等概念编写这种代码而不必被迫创建比其类似C的软件慢的软件,因此您经常找到优化器早在90年代就做了很好的内联(和递归)。
对于下一部分,它有些推测,因为我有点假设我所阅读和研究的编译器设计适用于我们生产质量的编译器。今天使用。谁知道他们究竟采用了什么样的高级技巧?
...但我相信编译器通常会在你达到机器代码级别之前内联代码。这是因为优化器的关键之一是有效的指令选择和寄存器分配。要做到这一点,它需要知道代码将在过程中使用的所有内存(变量)。它希望以一种有点抽象的形式,特定的寄存器尚未被选中,但已准备好分配。因此,在进入特定机器指令和寄存器的汇编领域之前,通常在此中间表示阶段完成内联,以便编译器可以在进行其神奇优化之前收集所有这些信息。它甚至可能在这里应用一些启发式方法来尝试&#39;尝试&#39;在实际执行之前内联或展开代码分支。
许多链接器甚至可以内联代码,我不确定它是如何工作的。我认为,当他们可以做到这一点时,目标代码实际上仍然是一种中间表示形式,仍然有点抽象远离特定的机器级指令和寄存器。然后链接器仍然可以在目标文件之间移动该代码并内联它,将代码生成/优化过程推迟到之后。