如果函数包含静态变量,为什么编译器不执行内联?

Why does the compiler not perform inlining, if the function contains static variables?

本文关键字:执行 编译器 为什么 包含 函数 静态 变量 如果      更新时间:2023-10-16

我从以下网站上读到,当函数具有静态变量时,编译器可能不会执行内联。原因是什么?

参考:C++中的内联函数

请记住,内联只是对编译器的请求,而不是命令。编译器可以忽略内联请求。编译器在以下情况下可能不会执行内联:

  1. 如果函数包含循环。(为,同时,做同时(
  2. 如果函数包含静态变量。
  3. 如果函数是递归的。
  4. 如果函数返回类型不是 void,并且函数体中不存在 return 语句。
  5. 如果函数包含 switch 或 goto 语句。

通常,编译器将决定内联和不内联的内容。内联关键字的用途已更改为允许某些内容进行多个定义。

这是来自cpp偏好的引用

inline 关键字的最初意图是作为优化器的指示器,即函数的内联替换优先于函数调用,也就是说,不是执行函数调用 CPU 指令将控制权转移到函数体,而是执行函数体的副本而不生成调用。这避免了函数调用(传递参数和检索结果(产生的开销,但它可能会导致更大的可执行文件,因为函数的代码必须重复多次。

由于关键字 inline 的这种含义是非绑定的,因此编译器可以自由地对任何未标记为内联的函数使用内联替换,并且可以自由生成对标记为内联的任何函数的函数调用。这些优化选择不会更改上面列出的有关多个定义和共享静态数据的规则。

因为函数的关键字inline的含义意味着"允许多个定义"而不是"首选内联",所以该含义扩展到变量。

编译器可以使用循环静态变量、switch 语句甚至递归函数完全内联函数。

下面是一个示例:

#include <iostream>
inline int foo(int* a, int n)
{
int r = 0;
static int b;
for (int i = 0; i < n; i++)
{
r += a[i];
}
switch (n)
{
case 42:
std::cout << "???n";
}
return r;
}
inline int foo2(int n)
{
return n == 0 ? 0 : 1 + foo2(n - 1);
}
int main()
{
int bar[3];
for (int i = 0; i < 3; i++)
{
std::cin >> bar[i];
}
std::cout << foo(bar, 3) << 'n';
std::cout << foo2(bar[0]) << 'n';
}

下面是编译器生成的汇编代码:

main:
sub     rsp, 24
mov     edi, OFFSET FLAT:_ZSt3cin
lea     rsi, [rsp+4]
call    std::basic_istream<char, std::char_traits<char> >::operator>>(int&)
lea     rsi, [rsp+8]
mov     edi, OFFSET FLAT:_ZSt3cin
call    std::basic_istream<char, std::char_traits<char> >::operator>>(int&)
lea     rsi, [rsp+12]
mov     edi, OFFSET FLAT:_ZSt3cin
call    std::basic_istream<char, std::char_traits<char> >::operator>>(int&)
mov     esi, DWORD PTR [rsp+8]
mov     edi, OFFSET FLAT:_ZSt4cout
add     esi, DWORD PTR [rsp+4]
add     esi, DWORD PTR [rsp+12]
call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov     edx, 1
lea     rsi, [rsp+3]
mov     BYTE PTR [rsp+3], 10
mov     rdi, rax
call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
mov     esi, DWORD PTR [rsp+4]
mov     edi, OFFSET FLAT:_ZSt4cout
call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
lea     rsi, [rsp+3]
mov     edx, 1
mov     BYTE PTR [rsp+3], 10
mov     rdi, rax
call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xor     eax, eax
add     rsp, 24
ret
_GLOBAL__sub_I_main:
sub     rsp, 8
mov     edi, OFFSET FLAT:_ZStL8__ioinit
call    std::ios_base::Init::Init() [complete object constructor]
mov     edx, OFFSET FLAT:__dso_handle
mov     esi, OFFSET FLAT:_ZStL8__ioinit
mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add     rsp, 8
jmp     __cxa_atexit

请注意,在程序集代码中,main函数中没有对foofoo2的调用。数组元素的添加由main函数中间的指令mov esi, DWORD PTR [rsp+8]add esi, DWORD PTR [rsp+4]add esi, DWORD PTR [rsp+12]执行。这篇文章要么是错误的,要么是用"可能不"来表示"可能不"而不是"不允许"。后一种情况是有意义的,因为编译器不太可能内联更大和更复杂的函数。

此外,如其他答案中所述,编译器可以在没有inline关键字的情况下内联函数。如果从上面的示例中删除inline关键字,编译器仍将内联该函数。

当文章说"可能无法执行"时,我认为它的意思是"可能不会执行",因为该短语在"可以忽略"之后不久出现。如果是这种情况,编译器实际上不需要不内联函数的理由。编译器自行决定内联。

尽管如此,编译器做什么和不做什么通常是有原因的。 较新的编译器比旧的编译器更擅长内联函数。本文的作者可能尝试了一个编译器,该编译器只是缺乏对带有静态变量的内联函数的支持。我希望较新的编译器没有此限制。

在评估编译器的局限性时,请记住信息的年龄。我没有看到那篇文章的日期。不过,它的评论可以追溯到三年前(它的信息似乎比这更古老(。三年内可能会发生很多事情。编译器不断发展并变得更好。原本不可能的事情可能已经司空见惯。

更不用说那篇文章的主题是偏离基础的。至少从 C++98 年开始(二十二年前(,C++ 中的inline关键字就与函数是否内联无关。