应该永远不要使用静态内联函数吗?


70

使用inline关键字有两个含义(第7.1.3 / 4节):

  1. 提示编译器,在调用点替换函数主体比通常的函数调用机制更可取。
  2. 即使省略了内联替换,也要遵循内联的其他规则(尤其是一个定义规则)。

通常,如果需要,任何主流的编译器都会在调用时替换函数体,因此inline#1真正不需要仅将函数标记为。

更进一步 #2,据我了解,当您将一个函数声明为static inlinefunction时,

static对功能力关键字inline函数有一个内部连接(内联函数具有外部连接)这样的功能中的每一个实例被视为一个单独的功能(各功能的地址是不同的)和这些功能中的每一种情况下都有自己的副本静态局部变量和字符串文字(内联函数只有这些的一个副本

因此,这种功能就像其他任何功能一样 static功能,并且关键字inline不再具有重要性,因此变得多余。

因此,实际上标记功能staticinline两者都没有用。应该是static不是最优选的)还是inline最优选的),
那么,在一个函数上使用staticinline在一起实际上是没有用的吗?


2
该代码段“以及这些函数的每个实例都有自己的静态局部变量和字符串文字副本”从何而来?
乔纳斯·比斯特罗姆(JunasByström)2012年

3
这是标准所说的吗?>>通常,如果需要,任何主流的编译器都会在调用时替代函数体,因此,真正不需要仅将函数内联标记为#1。
Nawaz 2012年

2
@JonasByström:第7.1.3 / 4节 “如果在一个翻译单元中内联声明了具有*外部链接的函数,则应在出现该功能的所有翻译单元中将其声明为内联;无需诊断。具有外部链接的内联函数在所有翻译单元中应具有相同的地址。extern内联函数中的静态局部变量始终引用同一对象。extern内联函数主体中字符串文字是不同翻译单元中的同一对象*“。标记的功能static没有外部链接,因此适用逆运算。
Alok保存

1
@Nawaz:不,这不是标准所说的(我无处声称它是这样),我说的是它基于大多数主流编译器的行为,通常该标准对实现没有任何说明,它只谈论可观察的行为。这个问题与实用性有关。
Alok保存

3
为了清楚起见,我想补充一点,这个问题是关于static inline不是类方法的函数的。有关static inline 方法,请看这里
山姆考夫曼

Answers:


60

您的分析是正确的,但不一定意味着无用。即使大多数编译器自动执行内联函数(原因1),也最好声明inline仅其意图。

忽略与的交互inlinestatic应谨慎使用功能。static以前不建议使用命名空间范围的修饰符,而使用未命名的命名空间(C ++ 03§D.2)。由于某种晦涩的原因,我不记得它在C ++ 11中已从弃用中删除,但您很少需要它。

因此,实际上将函数标记为static和inline根本没有用。它应该是静态的(不是最喜欢的)或内联的(最喜欢的),

没有偏好的概念。static意味着具有相同签名的不同功能可能存在于不同的.cpp文件(翻译单元)中。inline没有static意味着不同的翻译单元可以使用相同的定义定义相同的功能。

什么最好是使用不具名命名空间,而不是static

namespace {
    inline void better(); // give the function a unique name
}

static inline void worse(); // kludge the linker to allowing duplicates

3
@Als:无论它出现在什么地方,对于代码的维护者来说,对于声明代码的预期或假定行为更有用。对于代码用户而言,它并不是那么有用-函数的内联与否(理想情况下)对他们而言并不重要。
Mac

1
@Als:staticnamespace{}(大致)做同样的事情。inline还有其他事情。不要低估描述意图的价值。如果必须具有效果,则inline声明可能会使编译器对内联更具攻击性。到目前为止,听起来您似乎已经误用了这些说明符,将其用于a中的声明和a中的.h定义的函数.cpp,这是错误的。
Potatoswatter 2012年

3
没有命名空间优先于静态的概念。两者对连锁的作用相同。
Johannes Schaub-litb 2012年

1
@ JohannesSchaub-litb好吧,它们不一定完全一样。它们在C ++中的TU之间具有相同的可见性,但是ABI /二进制格式的效果可能有所不同。static不能保证生成像namespace{}is这样的可见唯一名称。拒绝使用的原因是什么?是ADL名称关联,是需要具有语言链接功能还是其他?如果是ADL,inline名称空间是否可以解决此问题?
Potatoswatter 2012年

1
“首选使用未命名的名称空间而不是静态名称空间:”-首选,为什么?两种版本均不应生成在TU外部可见的名称。
MM

27

静态和内联是正交的(独立的)。静态意味着该函数在翻译单元之外不应该可见,内联是对编译器的提示,程序员希望将其内联。这两个无关。

如果static inline未在翻译单元外部使用内联函数,则使用有意义。通过使用它,您可以通过在另一个具有相同名称的翻译单元中命名另一个内联函数来防止意外违反ODR规则的情况。

例:

source1.cpp:

inline int Foo()
{
  return 1;
}

int Bar1()
{
  return Foo();
}

source2.cpp:

inline int Foo()
{
  return 2;
}

int Bar2()
{
  return Foo();
}

没有在Foo上使用static(或没有使用匿名命名空间,这是大多数C ++程序员首选的方式),此示例违反了ODR,并且结果是不确定的。您可以使用Visual Studio测试Bar1 / Bar2的结果取决于编译器设置-在Debug配置中,Bar1和Bar2都将返回相同的值(内联未使用,链接器随机选择一个实现),在Release配置中,每个将返回预期值。


static inline当未在翻译单元之外使用内联函数时,使用才有意义。” static是有道理的,添加inline它是没有意义的。
Z玻色子

16

我可能对此并不完全正确,但是据我所知,声明函数 static inline是使(或允许)编译器生成机器代码的唯一方法,而该函数实际上根本没有在编译代码中定义,并且您所拥有的只是将函数调用直接替换为一系列指令,就像只是一个常规过程主体,而相对于源代码中的函数定义而言,过程调用的机器代码中没有任何痕迹。

也就是说,仅凭static inline您真正可以替代使用宏,仅凭其inline本身是不够的。

通过简单的Google搜索“静态内联”,您将看到有关此内容的编译器文档页面。我想这应该足以回答您的问题,并说:“不,实际上并不是没有用的”。这是一个讨论inline,尤其是http://www.greenend.org.uk/rjk/tech/inline.html的用法的站点示例static inline


3
有趣的static inline是,在此linux内核样式指南下,特别建议您建议使用宏替代。(“静态内联函数比宏更受青睐。它们提供类型安全性,没有长度限制,没有格式限制,并且在gcc下它们与宏一样便宜。”)
罗伯特·卡尔霍恩

2
C中的inline关键字与C ++具有不同的语义。在C中具有带有外部链接的内联函数会更加复杂,因为您必须手动选择放置非内联版本的翻译单元/目标文件。我相信使用静态内联的建议是以C为中心的,并且主要是出于方便。大概会增加二进制代码重复的风险
Arvid

如果要替换宏,extern inline则更合适。不同之处在于,static inline它生成的版本不是内联的,编译器可以选择使用其中一个。使用extern inline,非内联版本甚至不存在。
Eyal

12

如果您谈论自由函数(namespace范围),那么您的假设是正确的。static inline函数确实没有太大的价值。所以static inline是一个简单的static功能,它可以自动满足ODR和inline是多余的ODR目的。

但是,当我们谈论成员方法(class作用域)时,该static inline函数确实具有该值。
一旦将class方法声明为inline,则该方法的整体必须对所有翻译单元可见,包括class

请记住,static关键字用于时具有不同的含义class
编辑:您可能知道staticaclass内部的函数没有内部链接,换句话说,一个类不能具有其static方法的不同副本,具体取决于翻译(.cpp)单位
但是staticnamespace/ global范围内的自由功能确实每个翻译单元都有不同的副本。

例如

// file.h
static void foo () {}
struct A {
  static void foo () {}
};

// file1.cpp
#include"file.h"
void x1 ()
{
  foo();  // different function exclusive to file1.cpp
  A::foo();  // same function
}

// file2.cpp
#include"file.h"
void x2 ()
{
  foo();  // different function exclusive to file2.cpp
  A::foo();  // same function
}

我不明白您的回答,请您详细说明什么不同和如何不同?
Alok保存

1
如果不同意,内联是没有用的。它“仅”是一个提示,但仍然是一个提示,编译器为该提示提供了一些价值。
Suma 2012年

“编译器通常会忽略该提示”。这取决于编译器和使用的设置。当您将Visual Studio与/ Ob1一起使用时,编译器将遵守提示。
苏马(Suma)2012年

2
@Suma,您的意思是,如果您暗示inlineMSVS也会用内联一个递归函数调用(OP中的#1效果)/Ob1?不可能。在其他情况下,函数可能不会被其定义所替代(例如,函数体太大且在不同地方调用)。提示没有任何价值。唯一保证的效果inline是#2(来自OP)。
iammilind

2
使用/ Ob1内联是可以内联函数的提示。如果未标记为内联函数,则永远不会内联该函数。
Suma 2012年

2

我刚刚阅读了gcc的手册页,其中特别指出了带有编译器标志的静态内联的使用。对于标志,它内联函数,如果它也是静态的,并且在每次调用它的实例中都内联,那么它将摆脱函数定义,该定义将永远不会在创建的目标文件中使用,从而减少了生成的代码的大小少了一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.