一个外部链接的内联函数是否真的是内联函数?

6

来自C18标准:

如果翻译单元中一个函数的所有文件作用域声明都包含内联函数说明符而没有 extern,则该翻译单元中的定义是“内联定义”。

然后我们看到:

具有外部链接的内联函数的声明可能导致外部定义,也可能导致仅在翻译单元内可用的定义。带有 extern 的文件作用域声明会创建一个外部定义。

我编写了一些代码来检查函数是否实际上是内联的。 我使用了这个限制来找出:

具有外部链接的内联函数的内联定义不得包含具有静态或线程存储期的可修改对象的定义,并且不得包含对具有内部链接的标识符的引用。

这是代码:

static int n = 5;

void inline foo() { n = 66; }
void inline foo();    // remove 'inline' in second version

int main() {
    return 0;
}

编译时出现警告,提示内联函数正在使用静态对象,这意味着 foo() 是一个内联函数,因此它提供了内联(而非外部)定义。但是,当我从指定行中删除 inline 说明符时,就不再收到警告了。根据标准,它不是内联定义,所以我猜它提供了外部定义。
标准没有说,或者至少我看不到的是,一个提供外部定义的内联函数是否停止成为内联函数。根据我的测试,它确实停止成为内联函数。
如果我的结论是正确的,那么另一个问题就出现了:为什么 extern inline 函数是无用的?
2个回答

3
我感觉需要回答自己,因为这比我一开始预期的更加复杂,而且在我写下问题之后,我的研究中出现了新的事实。以下更像是我的结论,但我感觉我走在正确的道路上。所以我需要分享。反馈和确认/拒绝将不胜感激。
首先,看一下这段代码:
void inline foo() { return; }

int main() {
    foo();
    return 0;
}

这段文字可能看起来很简单,但事实上它无法编译。实际上,它编译了,但在链接步骤中失败了。为什么呢?让我们阅读标准中那个难以理解的完整段落:
对于一个具有外部链接的函数,应用以下限制:如果一个函数声明带有内联函数说明符,则它必须在同一翻译单元中定义。如果一个翻译单元中的所有文件作用域声明都包括带有extern的内联函数说明符,则该翻译单元中的定义是一个内联定义。内联定义不提供函数的外部定义,并且不禁止另一个翻译单元中的外部定义。内联定义提供了一个替代外部定义的选择,翻译器可以使用它来实现同一翻译单元中对该函数的任何调用。调用该函数时使用内联定义还是外部定义是未指定的。
从“不确定函数调用是否使用内联定义还是外部定义”这句话中,我们得到了为什么它没有编译(链接)好的答案。我的实现(GCC)选择了外部版本。而链接器不知道这样的外部函数。
标准说内联定义“不会禁止另一个翻译单元中的外部定义”。实际上确实如此,但如果从当前翻译单元调用该函数并且实现选择调用外部版本,则必须在其他地方定义它。
那么,另一个问题出现了:如果实现选择调用外部定义或内联定义,为什么需要同时定义两者?嗯,我在GCC文档中找到了答案:你永远不知道哪个会被选择。例如,当没有指定优化器开关时,GCC选择外部版本。对于许多优化代码配置,将选择内联版本。
至于为什么内联外部函数可能是无用的问题,实际上它们并不是。外部函数也可以内联。请查看此文档:https://gcc.gnu.org/onlinedocs/gcc/Inline.html 一个外部的内联函数可以被其他翻译单元使用和内联,但它不会创建内联定义。内联定义只有在你想要有根据优化开关使用的函数的替代版本时才有用,例如。

然而,我认为标准对于外部内联函数的内联并不是非常清晰。例如,GCC所做的是:非静态内联函数不是内联函数,除非它们在函数的声明中(而不是外部定义中)有inlineextern说明符。


这个回答似乎没有解决你在问题中提出的任何问题,相反,你开始回答一些不同的代码并试图解释那段代码。已经有几个问题和答案了,例如在这里看。此外,你在回答中新问题的回答没有提到程序会导致未定义行为而不需要诊断的主要问题。(见6.9/5)。 - M.M

3
在问题中,您尝试在编译器中尝试事物来推断语言规则。这通常是一个不好的想法,因为(a)在许多情况下,违反规则的影响很难观察到,而且(b)编译器可能有漏洞。相反,标准是语言规则的权威来源,因此应该通过参考标准来回答问题。
继续说:您的代码存在 C11 6.7.4/3 的约束违规,这也是您在问题中引用的。约束违规的影响是编译器必须发出诊断,它已经发出了诊断。
然后您问及某些修改,我想您指的是以下代码:
static int n = 5;
void inline foo() { n = 66; }
void foo();
int main() { return 0; }

这段文字讨论了关于C++中函数定义的一些概念。第一个引用的句子(来自6.7.4/7)指出,foo()的定义不是内联定义,因为并非所有TU中的文件范围声明都包含不带externinline限定符。由于它不是内联定义,所以n = 66没有问题,代码是正确的。
另外一个问题是,具有外部定义的内联函数是否还算内联函数。答案是不算,因为在6.7.4/7中明确指出:“内联定义不会为函数提供外部定义”。可能你混淆了“内联函数定义”和“带有inline限定符的函数定义”这两个概念。
最后,提到了一个问题:为什么带有extern inline限定符的函数是无用的?
如果你指的是关键字extern inline,那是另一个话题,这个问题没有涉及到,请看这里。具有外部链接的内联函数绝对不是无用的。

所以,如果我理解正确的话,如果我想要内联一个函数,它必须在我调用内联函数的同一TU中定义。我不能内联定义在不同TU中的函数。是这样吗? - Pep
@PepeDeTicher 内联函数必须在调用它的每个TU中定义,这就是为什么它们通常放置在头文件中的原因。 - M.M
我理解如果提供了外部定义,那么它就不再是内联定义了。那么,在extern inline和什么都没有之间,我看不出有什么区别。我可以假设一个不是内联定义的内联函数将根本不会被内联,但仍然会“尽可能快地执行”,甚至也可能被内联吗?就像“提供外部定义,但如果可以内联它”? - Pep
“被内联”是由编译器自行决定的(无论函数是否标记为“inline”,或者是内联定义)。标准仅指定程序输出,而不指定如何实现该输出或实现速度。 - M.M
抱歉,SO出了点问题,说我的上一个评论没有发布。不确定为什么你一直在谈论“extern inline”,因为它不是问题的一部分,而且你也没有发布任何使用它的代码。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接