【问题标题】:static variables in an inlined function内联函数中的静态变量
【发布时间】:2010-09-16 04:24:50
【问题描述】:

我有一个在头文件中声明和定义的函数。这本身就是一个问题。当该函数未内联时,使用该标题的每个翻译单元都会获得该函数的副本,并且当它们链接在一起时,就会出现重复。我通过使函数内联来“修复”该问题,但恐怕这是一个脆弱的解决方案,因为据我所知,即使您指定了“内联”关键字,编译器也不保证内联。如果这不是真的,请纠正我。

无论如何,真正的问题是,这个函数中的静态变量会发生什么?我最终会得到多少份?

【问题讨论】:

    标签: c++ header linker inline-functions


    【解决方案1】:

    我猜你在这里遗漏了一些东西。

    静态函数?

    将函数声明为静态将使其在其编译单元中“隐藏”。

    具有命名空间范围 (3.3.6) 的名称如果是以下名称,则具有内部链接

    ——显式声明为静态的变量、函数或函数模板;

    3.5/3 - C++14 (n3797)

    当名称具有内部链接时,它所表示的实体可以被同一翻译单元中其他范围的名称引用。

    3.5/2 - C++14 (n3797)

    如果你在一个头文件中声明这个静态函数,那么包括这个头文件在内的所有编译单元都会有自己的函数副本。

    问题是,如果该函数内有静态变量,则包括该头文件在内的每个编译单元也将有自己的个人版本。

    内联函数?

    将其声明为 inline 使其成为内联的候选对象(现在在 C++ 中这并不意味着很多,因为编译器会内联或不内联,有时会忽略关键字 inline 存在或不存在的事实):

    带有 inline 说明符的函数声明(8.3.5、9.3、11.3)声明了一个内联函数。 inline 说明符向实现表明,在调用点对函数体进行内联替换优于通常的函数调用机制。在调用点执行此内联替换不需要实现;但是,即使省略了这个内联替换,7.1.2 中定义的内联函数的其他规则仍应遵守。

    7.1.2/2 - C++14 (n3797)

    在头文件中,它有一个有趣的副作用:内联函数可以在同一个模块中定义多次,并且链接器将简单地将“它们”合并为一个(如果由于编译器的原因它们没有被内联)。

    对于内部声明的静态变量,标准中明确规定了一个,而且只有一个:

    extern 内联函数中的静态局部变量总是引用同一个对象。

    7.1.2/4 - C++98/C++14 (n3797)

    (函数默认为 extern,因此,除非您特别将函数标记为静态,否则这适用于该函数)

    这具有“静态”的优点(即可以在标头中定义)而没有缺陷(如果没有内联,它最多存在一次)

    静态局部变量?

    静态局部变量没有链接(它们不能在其范围之外通过名称引用),但具有静态存储期限(即它是全局的,但其构造和销毁遵循特定规则)。

    静态+内联?

    混合 inline 和 static 将产生您所描述的后果(即使函数是内联的,内部的静态变量也不会,并且您将使用与编译单元一样多的静态变量结束,包括定义你的静态函数)。

    回答作者的附加问题

    自从我写了这个问题后,我就使用 Visual Studio 2008 进行了尝试。我尝试打开所有使 VS 符合标准的选项,但我可能错过了一些选项。结果如下:

    当函数只是“内联”时,静态变量只有一个副本。

    当函数是“静态内联”时,有多少个翻译单元就有多少个副本。

    现在真正的问题是,事情是否应该如此,或者这是否是 Microsoft C++ 编译器的特性。

    所以我想你有类似的东西:

    void doSomething()
    {
       static int value ;
    }
    

    你必须意识到,函数内部的静态变量,简单地说,一个全局变量,除了函数的作用域外,对所有变量都隐藏,这意味着只有它在内部声明的函数才能到达它。

    内联函数不会改变任何东西:

    inline void doSomething()
    {
       static int value ;
    }
    

    只有一个隐藏的全局变量。编译器会尝试内联代码这一事实不会改变只有一个全局隐藏变量这一事实。

    现在,如果你的函数被声明为静态的:

    static void doSomething()
    {
       static int value ;
    }
    

    那么它对于每个编译单元都是“私有的”,这意味着每个 CPP 文件,包括声明静态函数的头文件,都将拥有它自己的函数私有副本,包括它自己的全局隐藏变量的私有副本,因此如有很多变量,因为有包括头在内的编译单元。

    将“内联”添加到内部带有“静态”变量的“静态”函数:

    inline static void doSomething()
    {
       static int value ;
    }
    

    就内部的静态变量而言,与不添加“inline”关键字的结果相同。

    所以VC++的行为是正确的,你误会了“内联”和“静态”的真正含义。

    【讨论】:

    • 我认为你错过了一个重要的点,即在链接阶段,在内联函数中声明的所有静态变量都将被解析为一个,我错了吗?
    • 不,因为每个静态变量都在其自己的单独函数中:尽管函数名称相同,但它们具有内部链接,因此不跨翻译单元共享。
    • @paercebal in inline void doSomething() { static int value ; },函数有外部联动;如果它出现在来自两个不同单元的标头中,则这是 ODR 违规
    • @M.M 你是什么意思?你的函数是inline,它不能违反ODR。
    • @Ruslan 那是不合理的
    【解决方案2】:

    我相信编译器会创建变量的许多副本,但链接器会选择一个并让所有其他人引用它。当我尝试创建不同版本的内联函数时,我得到了类似的结果。如果函数实际上没有内联(调试模式),则所有调用都会转到同一个函数,而不管它们是从哪个源文件调用的。

    像编译器一样思考一下——否则怎么可能呢?每个编译单元(源文件)相互独立,可以单独编译;因此,每个人都必须创建一个变量的副本,认为它是唯一的一个。链接器能够跨越这些边界并调整变量和函数的引用。

    【讨论】:

    • AFAICT,您在这里所说的完全正确。我不明白为什么人们不赞成这个答案。我唯一的猜测是他们读到“变量的许多副本”,然后停止! :( 无论如何,我的令牌(+1)。
    • 当人们询问编译器是什么意思时,他们的意思是编译器 + 链接器,因为您无法运行目标文件。所以这个答案是正确的,但完全没有意义。
    • 因为人们是无知的。这是一个更高级的问题,所有人都应该在讨论中做出区分。
    • 其实这个答案很有意义。它回答了“意思”的问题,但仍然使用正确的术语。
    • 我刚刚使用 gcc v9.3.0 对此进行了测试,它确实为每个文件包含静态变量的副本,即使在链接和剥离之后,函数也被内联。所以在我的例子中,我有 3 个源文件,每个源文件都使用相同的内联函数。内联函数的静态数据在编译后的二进制文件中出现了 3 次(使用-s -O2 编译)。 clang v10.0.0 做了同样的事情。
    【解决方案3】:

    我发现 Mark Ransom 的回答很有帮助 - 编译器创建了静态变量的许多副本,但链接器选择了一个副本并在所有翻译单元中强制执行它。

    我在其他地方发现了这个:

    参见 [dcl.fct.spec]/4

    [..] 具有外部链接的内联函数应具有相同的 所有翻译单元中的地址。外部变量中的静态局部变量 内联函数总是引用同一个对象。中的字符串文字 extern 内联函数是不同翻译单元中的同一个对象。

    我没有要检查的标准副本,但它与我在 VS Express 2008 中检查程序集的经验相符

    【讨论】:

      【解决方案4】:

      应该是这样的。 “static”告诉编译器您希望函数在编译单元中是本地的,因此您希望每个编译单元一份副本,每个函数实例一份静态变量的副本。

      "inline" 用于告诉编译器你希望函数被内联;现在,它只是认为“如果有多个代码副本就可以,只要确保它是相同的功能”。所以大家共享静态变量。

      注意:此答案是针对原发帖者发布给自己的答案而写的。

      【讨论】:

      • 他在询问“内联函数”中的“静态变量”,而不是静态函数中的变量。
      • 我们同意这一点,但您是对的:需要进行编辑才能将答案重新置于上下文中。
      • 我也遇到了this。那么这两者中的哪一个呢? inline 导致函数被内联还是可以多副本?
      • @Vassilis 都是正确的,虽然inline 不会导致内联,它只是建议它,并且它允许多个定义(但不能在同一个编译中单位)。
      【解决方案5】:

      自从我写了这个问题后,我就使用 Visual Studio 2008 进行了尝试。我尝试打开所有使 VS 符合标准的选项,但我可能错过了一些选项。结果如下:

      当函数只是“内联”时,静态变量只有一个副本。

      当函数是“静态内联”时,有多少个翻译单元就有多少个副本。

      现在真正的问题是,事情是否应该如此,或者这是否是 Microsoft C++ 编译器的特质。

      【讨论】:

      • “当函数是“静态内联”时,”——您的原始帖子没有说明这样做。您应该期待不同的结果,因为函数上的静态与变量上的静态具有不同的含义。函数上的静态意味着其他翻译单元不会看到这个定义。
      • 不确定您的设置,但在这种情况下编译器工作正常。但是,您可能希望包含一个单元测试,以防您遇到一些不符合要求的编译器。
      【解决方案6】:

      内联意味着可执行代码(指令)被内联到调用函数的代码中。无论您是否要求,编译器都可以选择这样做。这对函数中声明的变量(数据)没有影响。

      【讨论】:

        【解决方案7】:

        我相信您最终会得到每个翻译单元一个。您实际上已经获得了该函数(及其声明的静态变量)的许多版本,每个包含标题的翻译单元都有一个版本。

        【讨论】:

          【解决方案8】:

          静态意味着一个副本分布在整个程序中,而内联意味着它需要在同一个程序中多次使用相同的代码,因此不能在内联函数中将变量设为静态。

          【讨论】:

            【解决方案9】:

            除了这一切可能暗示的任何设计问题,由于您已经被它所困扰,在这种情况下您应该使用静态而不是内联。这样,每个人都共享相同的变量。 (静态函数)

            【讨论】:

            • 这个答案是错误的。静态与您描述的效果相反。
            猜你喜欢
            • 1970-01-01
            • 2016-04-27
            • 2017-09-02
            • 1970-01-01
            • 2016-08-30
            • 2013-11-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多