没有"静态"或"外部"的"内联"在C99中是否有用?

Sve*_*ach 91 c inline c99

当我尝试构建此代码时

inline void f() {}

int main()
{
    f();
}
Run Code Online (Sandbox Code Playgroud)

使用命令行

gcc -std=c99 -o a a.c
Run Code Online (Sandbox Code Playgroud)

我收到链接器错误(未定义引用f).如果我使用static inlineextern inline代替just inline,或者如果我使用-O(因此函数实际内联),则错误消失.

这种行为似乎在C99标准第6.7.4(6)段中定义:

如果转换单元中函数的所有文件范围声明都包含inline函数说明符extern,则该转换单元中的定义是内联定义.内联定义不提供函数的外部定义,也不禁止另一个转换单元中的外部定义.内联定义提供了外部定义的替代,翻译器可以使用该定义在同一翻译单元中实现对该功能的任何调用.未指定对函数的调用是使用内联定义还是使用外部定义.

如果我理解了这一切,inline那么如果还有一个具有相同名称的外部函数,那么具有上例中定义的函数的编译单元只会一致地编译,而且我永远不知道我自己的函数或外部函数是否被调用.

这种行为不是完全愚蠢吗?在inline没有staticextern在C99中定义函数是否有用?我错过了什么吗?

答案摘要

当然我错过了什么,行为并不愚蠢.:)

正如Nemo所解释的那样,我们的想法是定义函数

inline void f() {}
Run Code Online (Sandbox Code Playgroud)

在头文件中只有一个声明

extern inline void f();
Run Code Online (Sandbox Code Playgroud)

在相应的.c文件中.只有extern声明才会触发生成外部可见的二进制代码.确实没有inline在.c文件中使用 - 它只在标题中有用.

正如Jonathan的回答中引用的C99委员会基本原理所阐述的那样,inline所有关于编译器优化都需要在调用的站点上看到函数的定义.这只能通过将标题放在标题中来实现,当然,标题中的定义在编译器每次看到它时都不能发出代码.但由于编译器不是强制实际内联函数,因此必须在某处存在外部定义.

Nem*_*emo 38

实际上,这个优秀的答案也回答了你的问题,我想:

外部内联

这个想法是"内联"可以在头文件中使用,然后在.c文件中使用"extern inline"."extern inline"就是如何指示编译器哪个目标文件应包含(外部可见)生成的代码.

[更新,详细说明]

我不认为.c文件中的"内联"(没有"静态"或"外部")有任何用处.但是在头文件中它是有意义的,并且它需要在某些.c文件中相应的"extern inline"声明来实际生成独立代码.

  • 是的,我直到现在才明白这一点,所以谢谢你问:-).奇怪的是,非外部"内联"_definition_在标题中(但不一定导致任何代码生成),而"extern inline"_declaration_在.c文件中进行,实际上导致代码生成. (6认同)
  • 这是否意味着我在头文件中写入`inline void f(){}`和.c文件中的`extern inline void f();` 所以实际的函数定义在标题中,而.c文件在这种情况下只包含一个声明,与通常的顺序相反? (2认同)
  • @KunWu:`inline f()` 定义实际上并没有发出任何代码,因此它会在链接时产生一个未定义的符号。`static` 关键字导致定义发出(本地可见)代码。这些是语义...优化编译器实际上可能在任何一种情况下内联扩展代码,甚至没有`inline`关键字。在 C 中,`inline` 关键字更多地与作用域和链接规则有关,而不是与实际内联有关。 (2认同)
  • @MatthieuMoy 您需要使用 `-std=c99` 而不是 `-std=gnu89`。 (2认同)

Jon*_*ler 25

从标准(ISO/IEC 9899:1999)本身:

附录J.2未定义的行为

  • ...
  • 具有外部链接的inline函数使用函数说明符声明,但不在同一个转换单元中定义(6.7.4).
  • ...

C99委员会写了一个理由,它说:

6.7.4函数说明符

C99的一个新特性:inline关键字改编自C++,是一个函数说明符,只能在函数声明中使用.它对于需要在调用的站点上显示函数定义的程序优化很有用.(请注意,标准不会尝试指定这些优化的性质.)

如果函数具有内部链接,或者如果它具有外部链接并且调用与外部定义位于相同的转换单元中,则可确保可见性.在这些情况下,inline函数的声明或定义中关键字的存在 除了指示优先选择优先调用该函数的调用优先于不使用inline关键字声明的其他函数的调用之外没有效果.

可见性是调用具有外部链接的函数的问题,其中调用与函数的定义位于不同的转换单元中.在这种情况下,inline关键字允许包含调用的翻译单元还包含函数的本地或内联定义.

程序可以包含具有外部定义的翻译单元,具有内联定义的翻译单元,以及具有声明但没有函数定义的翻译单元.后一个翻译单元中的调用将照常使用外部定义.

函数的内联定义被认为是与外部定义不同的定义.如果在内func联定义可见的情况下调用某个具有外部链接的函数,则行为与调用另一个函数(例如__func,使用内部链接)的行为相同 .符合规定的程序不能取决于调用哪个函数.这是标准中的内联模型.

符合规范的程序不能依赖于使用内联定义的实现,也不能依赖于使用外部定义的实现.函数的地址始终是与外部定义对应的地址,但是当使用此地址调用函数时,可能会使用内联定义.因此,以下示例可能不会按预期方式运行.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}
Run Code Online (Sandbox Code Playgroud)

由于实现可能对其中一个调用使用内联定义,而对另一个调用saddr使用外部定义,因此不能保证相等操作的计算结果为1(true).这表明在内联定义中定义的静态对象与外部定义中的对应对象不同.这促使了对甚至定义const这种类型的非对象的约束.

内联以这样的方式添加到标准中,即它可以使用现有的链接器技术实现,并且C99内联的子集与C++兼容.这是通过要求将包含内联函数定义的恰好一个转换单元指定为为函数提供外部定义的转换单元来实现的.因为该说明书简单地包括,要么缺乏声明的inline关键字,或同时包含inlineextern,它也将通过一个C++翻译接受.

C99中的内联确实以两种方式扩展了C++规范.首先,如果inline在一个翻译单元中声明了一个函数 ,则不需要inline在每个其他翻译单元中声明它.例如,这允许库函数在库中内联,但仅通过其他地方的外部定义可用.对外部函数使用包装函数的替代方法需要一个额外的名称; 如果翻译者实际上没有进行内联替换,它也可能会对性能产生负面影响.

其次,要求内联函数的所有定义"完全相同"的要求被程序行为不应取决于是否使用可见内联定义或外部定义实现调用的要求所取代.功能.这允许内联定义专门用于在特定翻译单元内使用.例如,库函数的外部定义可能包括一些参数验证,这些验证对于从同一库中的其他函数进行的调用不需要.这些扩展确实提供了一些优势; 关注兼容性的程序员可以简单地遵守更严格的C++规则.

请注意,这是恰当的实现来提供的标准头文件标准库函数内置定义,因为这会破坏一些包括他们的头后redeclares标准库函数的一些遗留代码.该inline关键字仅用于为用户提供建议内联函数的可移植方式.因为标准头文件不需要是可移植的,所以实现还有其他选择:

#define abs(x) __builtin_abs(x)
Run Code Online (Sandbox Code Playgroud)

或其他用于内联标准库函数的非可移植机制.

  • 再次感谢.我从答案中得到的最有价值的见解是,有一份文件包含C99标准的基本原理(可能还有其他标准的文件).虽然Nemo的答案给了我一条鱼,但是这条鱼教我钓鱼. (4认同)