3

如果我尝试编译以下 C 代码而不声明函数静态,则会收到链接器错误:

undefined reference to '_fun'

但如果我不让它成为静态的,它就会起作用。static在 c++ 中,没有关键字它就可以正常工作。

// Doesn't work
inline int fun()
{
    return 3;
}

// Works
static inline int fun()
{
    return 3;
}

int main(int argc, const char * argv[])
{
    printf("%i", fun());
}
4

2 回答 2

3

C 中的要求inline由 ISO C 标准的第 6.7.4 节定义。从N1256引用本节

任何具有内部链接的函数都可以是内联函数。对于具有外部链接的函数,适用以下限制: 如果函数使用内联函数说明符声明,则它也应在同一翻译单元中定义。如果翻译单元中函数的所有文件范围声明都包含 不带extern的内联函数说明符,则该翻译单元中的定义是内联定义. 内联定义不为函数提供外部定义,也不禁止在另一个翻译单元中进行外部定义。内联定义提供了外部定义的替代方案,翻译器可以使用它来实现对同一翻译单元中函数的任何调用。未指定对函数的调用是使用内联定义还是外部定义。

据我所知,您的定义满足所有这些要求。这个:

inline int fun()
{
    return 3;
}

既是 的声明又是的定义fun如果没有inline关键字,它将具有外部链接。

棘手的部分是最后一句话:

未指定对函数的调用是使用内联定义还是外部定义。

在这种情况下,没有外部定义。您没有说您正在使用什么编译器,但gcc -std=c99 -pedantic显然默认情况下选择使用外部定义,并且由于没有,您会收到链接器错误。(没有-std=c99 -pedantic,就没有链接器错误,但那是因为 gcc 也inline作为 C90 之上的扩展来实现。)

如果您只打算在该一个源文件中使用该函数,则最好还是添加static关键字,以提供内部链接。

实验表明,如果我使用-O1-O2-O3.

C 标准中的一个脚注似乎暗示 gcc 行为正确。同一节中的示例具有类似的非static inline函数定义:

inline double cels(double t)
{
      return (5.0 * (t - 32.0)) / 9.0;
}

其次是:

因为cels具有外部链接并被引用,所以外部定义必须出现在另一个翻译单元中(参见 6.9);内联定义和外部定义是不同的,都可以用于调用。

该标准的意图似乎是,如果一个inline函数具有内部链接,则应该在使用它的源文件中只定义一次,但如果它具有外部链接,则内联定义是必须出现的非内联定义的替代方案别处。是调用外部函数还是扩展内联定义的选择由编译器决定。

有些点与您的问题没有直接关系:

int fun()应该是int fun(void)。空括号是合法的,但它们表明该函数采用未指定数量和类型的参数。指定它不接受任何参数,这void是您想要的。

#include <stdio.h>如果您要调用printf;则需要;这不是可选的。

你不想constargv. 就此而言,由于您不引用命令行参数,因此您可以编写int main(void).

于 2013-07-03T02:29:59.607 回答
0

C99inline语义很微妙——事实上,语言的整个部分(存储持续时间与链接、暂定和内联定义)都是一团糟。

虽然在包含存储类说明符(或)inline的定义中充当编译器提示并且基本上可以忽略,但如果没有说明符,语义会发生变化。staticextern

像这样的定义有inline int fun(void) { ... }两件事:

首先,它声明了一个带有外部链接的标识符,而不提供相应的外部定义。这意味着这样的定义必须由不同的翻译单元提供,否则我们最终会出现未定义的行为(可能表现为链接失败)。

其次,它提供了一个内联定义,它是外部定义的替代方案。由于函数体在当前翻译单元中是可见的,编译器可以使用它来内联函数或进行类型特化。

为了获得外部定义,直到最近,我认为有必要在另一个翻译单元中重复函数定义(或者用 4 行预处理器代码伪造它)。

但是,这不是必需的:一行代码——包含说明符的函数的重新声明extern——足以使内联定义成为外部定义。

在您的示例中,这意味着将

inline int foo(void)
{
    return 42;
}

进入头文件foo.h并提供foo.c包含内容的源文件

#include "foo.h"

// force definition to be external instead of inline
// I believe inline could be omitted, but it doesn't hurt
extern inline foo(void);

为什么这很有用?由于 C 缺少模板,泛型代码通常会带来性能损失,因为您需要使用void*函数指针(或者,在更复杂的情况下,vtables)来伪造泛型。

然而,一个足够聪明的优化器可以恢复模板的大部分(可能是所有)性能优势,但(在没有链接时优化的情况下)只有在当前翻译单元中相关函数定义可见时。

虽然这可以通过将static定义放在头文件中来实现,但这可能会以与 C++ 模板相同的方式将代码大小增加到不可接受的水平。

相比之下,使用 C99inline函数,编译器可以自由地忽略内联定义而支持外部定义,它甚至可以驻留在共享库中。

从中受益的函数的一个很好的例子是qsort(),在 .in 中有一个内联定义,stdlib.hlibc.so. 没有先验理由qsort()std::sort().

于 2013-07-03T08:38:28.070 回答