我从下面的网站上看到,当函数包含静态变量时,编译器可能不会执行内联。原因是什么?
参考资料 https:/www.geeksforgeeks.orginline-functions-cpp
记住,内联只是对编译器的请求,而不是命令。编译器可以忽略内联的请求。编译器可能不会在这样的情况下执行内联,比如。 1)如果一个函数包含一个循环。for、while、do-while) 2)如果一个函数包含静态变量。 3)如果一个函数是递归的。 4)如果一个函数的返回类型不是void,并且返回语句不存在于函数体中。 5)如果一个函数包含switch或goto语句。
一般情况下,编译器会决定哪些要内联,哪些不要内联。inline关键字的目的已经改变了,它的意思是允许某个东西有多个定义。
这里引用了一段话 cppreference
inline关键字的初衷是作为优化器的一个指示,即函数的内联替换优于函数调用,即不执行函数调用的CPU指令将控制权转移给函数体,而是执行函数体的副本而不产生调用。这样就避免了函数调用产生的开销(传递参数和检索结果),但由于函数的代码要重复多次,可能导致可执行文件较大。
由于关键字inline的这个含义是非约束性的,编译器可以自由地对任何没有标记为inline的函数使用inline替换,也可以自由地对任何标记为inline的函数产生函数调用。这些优化选择并不改变上面列出的关于多定义和共享静态的规则。
因为内联这个关键词对于函数的意义变成了 "允许多个定义 "而不是 "内联是首选",所以这个意义被扩展到了变量上。
当文章中说 "可能不执行 "时,我认为它的意思是 "可能不执行",因为这句话是在 "可以忽略 "之后不久出现的。如果是这样的话,编译器其实并不需要一个不内联函数的理由。编译器会根据自己的判断进行内联。
不过,编译器做什么,不做什么,通常还是有原因的。新的编译器比老的编译器更善于内联函数。有可能是文章作者实验的编译器根本不支持内联函数与静态变量。我希望新的编译器没有这个限制。
在评价编译器的局限性时,请记住信息的时代。我没有看到那篇文章的日期。不过它的评论可以延伸到三年前(它的信息似乎比这更早)。三年内可以发生很多事情。编译器在进化,变得更好。原本不可能的事情可能已经变得司空见惯。更何况那篇文章的一个主要主题是离谱的。该 inline
C++中的关键字至少从C++98(二十二年前)开始就与函数是否内联无关了。
编译器完全可以将循环静态变量、开关语句、甚至递归函数的函数内联。
下面是一个例子。
#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
请注意,在汇编代码中,没有调用 foo
或 foo2
在 main
函数。数组元素的添加是由以下指令执行的 mov esi, DWORD PTR [rsp+8]
, add esi, DWORD PTR [rsp+4]
和 add esi, DWORD PTR [rsp+12]
当中 main
功能。这篇文章要么是错的,要么就是用 "may not "来表示 "might not",而不是 "isn't allowed to"。后一种情况是有道理的,因为编译器不太可能内联较大和较复杂的函数。
另外,正如其他答案中所解释的那样,编译器可以在不使用 inline
关键字。如果您删除 inline
关键字,编译器仍然会内联该函数。