代码之家  ›  专栏  ›  技术社区  ›  Dirk Groeneveld

内联函数中的静态变量

  •  99
  • Dirk Groeneveld  · 技术社区  · 16 年前

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

    不管怎样,真正的问题是,这个函数中的静态变量会发生什么?我最终会得到多少份?

    9 回复  |  直到 10 年前
        1
  •  115
  •   paercebal    9 年前

    我猜你错过了什么,在这里。

    静态功能?

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

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

    明确声明为静态的变量、函数或函数模板;

    3.5/3-C++14(n3797)

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

    3.5/2-C++14(n3797)

    如果在标头中声明此静态函数,则包括此标头在内的所有编译单元都将拥有自己的函数副本。

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

    内联功能?

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

    带有内联说明符的函数声明(8.3.5,9.3,11.3)声明内联函数。内联说明符向实现表明,在调用点内联替换函数体比通常的函数调用机制更可取。在调用点执行此内联替换不需要实现;然而,即使省略了这种内联替换,7.1.2中定义的内联函数的其他规则仍应得到遵守。

    7.1.2/2-C++14(n3797)

    在头文件中,它有一个有趣的副作用:内联函数可以在同一个模块中多次定义,链接器只需将“它们”连接成一个(如果它们不是由于编译器的原因内联的)。

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

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

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

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

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

    静态局部变量?

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

    静态+内联?

    混合内联和静态将产生你所描述的后果(即使函数是内联的,里面的静态变量也不会内联,你最终会得到与编译单元(包括静态函数的定义)一样多的静态变量)。

    对作者补充问题的答复

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

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

    当函数是“静态内联”时,副本的数量与翻译单元的数量一样多。

    现在真正的问题是,事情是否应该这样,或者这是否是微软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 ;
    }
    

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

    因此,VC++的行为是正确的,你误解了“内联”和“静态”的真正含义。

        2
  •  42
  •   Mark Ransom    8 年前

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

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

        3
  •  13
  •   YSC    8 年前

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

    在其他地方,我发现了这一点:

    请参见[dcl.fct.spec]/4

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

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

        4
  •  5
  •   Raphaël Saint-Pierre    16 年前

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

    “内联”用于告诉编译器你希望函数内联;如今,它只是将其视为“如果有多个代码副本也没关系,只要确保它的功能是相同的”。所以每个人都共享静态变量。

    注:此答案是针对原始海报发布给自己的答案而写的。

        5
  •  3
  •   Dirk Groeneveld    16 年前

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

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

    当函数是“静态内联”时,副本的数量与翻译单元的数量一样多。

    现在真正的问题是,事情是否应该这样,或者这是否是微软C++编译器的意识形态。

        6
  •  -1
  •   Windows programmer    16 年前

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

        7
  •  -3
  •   Jason Etheridge    16 年前

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

        8
  •  -3
  •   Sanjeeb Kumar Sahu Sanjeeb Kumar Sahu    15 年前

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

        9
  •  -4
  •   Robert Gould    16 年前

    除了任何设计问题,这一切都可能意味着,既然你已经被它困住了,在这种情况下,你应该使用静态而不是内联。这样每个人都有相同的变量。(静态功能)

    推荐文章