在C99中没有“静态”或“外部”有用吗?
当我尝试构建这个代码
inline void f() {} int main() { f(); }
使用命令行
gcc -std=c99 -oa ac
我得到一个链接器错误(未定义的参考f
)。 如果我使用static inline
或外extern inline
而不是inline
,或者如果我使用-O
编译(因此函数实际内联),错误消失。
这个行为似乎是在C99标准的6.7.4(6)中定义的:
如果翻译单元中的某个函数的所有文件范围声明都包含没有
extern
的inline
函数说明符,则该翻译单元中的定义是内联定义。 内联定义不提供该函数的外部定义,也不禁止其他翻译单元中的外部定义。 内联定义提供了一个外部定义的替代方案,翻译人员可以使用该外部定义来实现对相同翻译单元中的功能的任何调用。 对函数的调用是使用内联定义还是外部定义是未指定的。
如果我正确地理解了这一点,那么在上面的例子中,一个带有inline
函数的编译单元只能在一个外部函数名称相同的情况下一致地编译,而且我永远不知道是否调用了我自己的函数或外部函数。
这种行为是不是完全愚蠢? 在C99中定义没有static
或extern
函数的函数是否有用? 我错过了什么吗?
答案摘要
当然,我错过了一些东西,行为并不傻。 🙂
正如Nemo解释的那样 ,这个想法是把这个功能的定义
inline void f() {}
在头文件中只有一个声明
extern inline void f();
在相应的.c文件中。 只有extern
声明触发了外部可见二进制代码的生成。 在.c文件中确实没有使用inline
– 它只在头文件中有用。
正如在Jonathan的答案中所引用的C99委员会的基本原理所阐明的那样, inline
就是编译器优化,它要求函数的定义在调用的站点上可见。 这只能通过将定义放在头文件中来实现,当然,头文件中的定义在编译器看到的时候不能发出代码。 但是由于编译器并没有强制实际内联一个函数,所以外部定义必须存在。
其实这个优秀的答案也回答你的问题,我想:
外部内联
这个想法是可以在头文件中使用“inline”,然后在.c文件中使用“extern inline”。 “extern inline”就是你如何指示编译器哪个目标文件应该包含(外部可见)生成的代码。
[更新,阐述]
我不认为在.c文件中有“inline”(没有“static”或“extern”)的用处。 但是在头文件中它是有意义的,它需要在一些.c文件中对应的“extern inline”声明来实际生成独立代码。
从标准(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 }
由于实现可能会对
saddr
的调用之一使用内联定义,并将外部定义用于另一个,所以相等操作不保证评估为1(真)。 这显示了内联定义中定义的静态对象与外部定义中的相应对象是不同的。 这激发了甚至定义这种非常量对象的约束。内联被添加到标准中,以便可以使用现有的链接器技术来实现内联,并且C99内联的一个子集与C ++兼容。 这是通过要求将包含内联函数的定义的恰好一个翻译单元指定为为该函数提供外部定义的翻译单元来实现的。 由于该规范仅由缺少
inline
关键字的声明组成,或者同时包含inline
和extern
,它也将被C ++翻译器接受。C99中的内联确实以两种方式扩展了C ++规范。 首先,如果一个函数在一个翻译单元中被
inline
声明,那么不需要在每个其他翻译单元中声明inline
。 例如,这允许一个库函数在库中内联,但只能通过其他地方的外部定义来使用。 为外部函数使用包装函数的替代方法需要额外的名称; 如果翻译人员没有实际进行内联替换,也可能对性能产生不利影响。其次,内联函数的所有定义“完全相同”的要求被程序的行为不应依赖于调用是使用可见内联定义还是外部定义来实现的要求所取代功能。 这允许内联定义专用于在特定翻译单元中的使用。 例如,库函数的外部定义可能包含一些参数验证,而这些参数验证对于来自同一库中的其他函数的调用不需要。 这些扩展提供了一些优势; 而关注兼容性的程序员可以简单地遵守更严格的C ++规则。
请注意,在标准头文件中提供标准库函数的内联定义是不合适的,因为这会破坏一些在包含头文件后重新声明标准库函数的遗留代码。
inline
关键字仅用于向用户提供建议内联函数的便携方式。 由于标准头文件不一定是可移植的,所以实现还有其他的选择:#define abs(x) __builtin_abs(x)
或其他用于内联标准库函数的非可移植机制。
>我得到一个链接器错误(未定义的参考 f
)
在这里工作:Linux x86-64,GCC 4.1.2。 可能是你的编译器的一个bug; 在引用的段落中,我没有看到禁止给定程序的标准。 请注意使用if而不是iff 。
内联定义提供了一个外部定义的替代方案 ,翻译人员可以使用该外部定义来实现对相同翻译单元中的功能的任何调用。
所以,如果你知道函数f
的行为,并且想要在紧密的循环中调用它,则可以将其定义复制粘贴到模块中以防止函数调用; 或者 ,您可以提供一个定义,为了当前模块的目的,它是等价的(但跳过输入验证,或任何可以想象的优化)。 然而,编译器编写器可以选择优化程序大小。