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

问题描述 投票:-2回答:1

我从下面的网站上看到,当函数包含静态变量时,编译器可能不会执行内联。原因是什么?

参考资料 https:/www.geeksforgeeks.orginline-functions-cpp

记住,内联只是对编译器的请求,而不是命令。编译器可以忽略内联的请求。编译器可能不会在这样的情况下执行内联,比如。 1)如果一个函数包含一个循环。for、while、do-while) 2)如果一个函数包含静态变量。 3)如果一个函数是递归的。 4)如果一个函数的返回类型不是void,并且返回语句不存在于函数体中。 5)如果一个函数包含switch或goto语句。

c++ c inline compiler-optimization
1个回答
3
投票

一般情况下,编译器会决定哪些要内联,哪些不要内联。inline关键字的目的已经改变了,它的意思是允许某个东西有多个定义。

这里引用了一段话 cppreference

inline关键字的初衷是作为优化器的一个指示,即函数的内联替换优于函数调用,即不执行函数调用的CPU指令将控制权转移给函数体,而是执行函数体的副本而不产生调用。这样就避免了函数调用产生的开销(传递参数和检索结果),但由于函数的代码要重复多次,可能导致可执行文件较大。

由于关键字inline的这个含义是非约束性的,编译器可以自由地对任何没有标记为inline的函数使用inline替换,也可以自由地对任何标记为inline的函数产生函数调用。这些优化选择并不改变上面列出的关于多定义和共享静态的规则。

因为内联这个关键词对于函数的意义变成了 "允许多个定义 "而不是 "内联是首选",所以这个意义被扩展到了变量上。


2
投票

当文章中说 "可能不执行 "时,我认为它的意思是 "可能不执行",因为这句话是在 "可以忽略 "之后不久出现的。如果是这样的话,编译器其实并不需要一个不内联函数的理由。编译器会根据自己的判断进行内联。

不过,编译器做什么,不做什么,通常还是有原因的。新的编译器比老的编译器更善于内联函数。有可能是文章作者实验的编译器根本不支持内联函数与静态变量。我希望新的编译器没有这个限制。

在评价编译器的局限性时,请记住信息的时代。我没有看到那篇文章的日期。不过它的评论可以延伸到三年前(它的信息似乎比这更早)。三年内可以发生很多事情。编译器在进化,变得更好。原本不可能的事情可能已经变得司空见惯。更何况那篇文章的一个主要主题是离谱的。该 inline C++中的关键字至少从C++98(二十二年前)开始就与函数是否内联无关了。


2
投票

编译器完全可以将循环静态变量、开关语句、甚至递归函数的函数内联。

下面是一个例子。

#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

请注意,在汇编代码中,没有调用 foofoo2main 函数。数组元素的添加是由以下指令执行的 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 关键字,编译器仍然会内联该函数。

© www.soinside.com 2019 - 2024. All rights reserved.