澄清 C 中内联函数的内部链接

Clarification over internal linkage of inline functions in C

理论上,inline 函数在 C 语言中有 internal/static linkage,也就是说,它们仅在单个翻译单元中可见。 因此,在两个单独的文件中定义的内联函数应该无法相互看到,并且两者都有自己的地址 space.

我正在尝试使用以下文件

***cat a.c*** 

#include <stdio.h>

inline int foo()
{
    return 3;
}

void g()
{
    printf("foo called from g: return value = %d, address = %#p\n", foo(), &foo);
}


***cat b.c***
#include <stdio.h>

inline int foo()
{
    return 4;
}

void g();

int main()
{
    printf("foo called from main: return value = %d, address = %#p\n", foo(), &foo);
    g();
    return 0;
}

gcc -c a.c
gcc -c b.c
gcc -o a.out a.o b.o

b.o: In function `foo':
b.c:(.text+0x0): multiple definition of `foo'
a.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

上面的编译错误反映了b.c能够看到a.cfoo的定义 并且编译失败(内部 linked 内联函数不应该是这种情况)。

如果我遗漏了什么,请帮助我理解。

编辑 1: 正在尝试这个 link 的理论。 https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rzarg/inline_linkage.htm

内联是一种优化,是否进行内联由编译器自行决定(如果可能的话)。所以不能保证符号不会在生成的目标文件中导出。为此目的使用 static

TL;DR: GCC 仍默认使用其旧语义 inline,其中 inline 函数仍被编译为外部可见实体.指定 -std=c99-std=c11 将导致 GCC 实现标准语义;但是,IBM 编译器也不符合标准。所以链接仍然会失败,但会出现不同的错误。


从 C99 开始,没有声明链接的函数声明不会生成函数对象。内联定义只会与内联替换一起使用,编译器没有义务执行此优化。预计函数的外部定义存在于其他一些翻译单元中,并且如果使用函数对象,则必须存在这样的定义,无论是通过获取其地址还是在编译器选择不执行的上下文中调用内联替换。

如果使用 staticextern 声明内联函数,则编译一个函数对象,具有指示的链接,从而满足定义函数对象的要求。

在 C99 之前,inline 不是 C 标准的一部分,但许多编译器——尤其是 GCC——将其作为扩展实现。然而,在 GCC 的情况下,inline 的语义与上述说明略有不同。

在 C99(及更新版本)中,没有链接规范的内联函数只是一个内联定义("An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit." §6.7.4p7)。但在 GCC 扩展中,没有链接规范的内联函数被赋予了外部链接(就像非内联函数声明一样)。 GCC 然后将 extern inline 特例化为 "do not generate a function object",这实际上与标准 C99 处理既没有 extern 也没有 static 修饰符的内联函数相同。请参阅 GCC manual,尤其是最后一节。

这仍然很重要,因为 GCC 仍然默认使用其原始的 inline 语义,除非您指定它应符合某些 C 标准(例如,使用 -std=c11)或禁用使用 -fno-gnu89-inline.

的 GNU 内联语义

据我所知,示例代码取自 IBM i7.1 编译器文档,并未正确反映任何 C 标准。 foo作为内联函数的两个定义不会生成任何名为foo的实际函数,因此&foo的使用必须引用一些外部定义的foo,并且没有'一个在节目中。如果您告诉 GCC 使用 C11/C99 语义,GCC 将报告此问题:

$ gcc -std=c99 a.c b.c
/tmp/ccUKlp5g.o: In function `g':
a.c:(.text+0xa): undefined reference to `foo'
a.c:(.text+0x13): undefined reference to `foo'
/tmp/cc2hv17O.o: In function `main':
b.c:(.text+0xa): undefined reference to `foo'
b.c:(.text+0x13): undefined reference to `foo'
collect2: error: ld returned 1 exit status

相比之下,如果您要求 Gnu 内联语义,两个翻译单元都会定义 foo,并且链接器会抱怨重复定义:

$ gcc -std=c99 -fgnu89-inline a.c b.c
/tmp/ccAHHqOI.o: In function `foo':
b.c:(.text+0x0): multiple definition of `foo'
/tmp/ccPyQrTO.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

另请注意,GCC 默认不内联任何函数。您必须提供一些优化选项才能启用函数内联。如果这样做,并且删除地址运算符的使用,则可以编译程序:

$ cat a2.c
#include <stdio.h>
inline int foo() { return 3; }
void g() {
    printf("foo called from g: return value = %d\n", foo());
}
$ cat b2.c
#include <stdio.h>
inline int foo() { return 4; }
void g();
int main() {
    printf("foo called from main: return value = %d\n", foo());
    g();
    return 0;
}

$ # With no optimisation, an external definition is still needed:
$ gcc -std=c11 a2.c b2.c
/tmp/cccJV9J6.o: In function `g':
a2.c:(.text+0xa): undefined reference to `foo'
/tmp/cct5NcjY.o: In function `main':
b2.c:(.text+0xa): undefined reference to `foo'
collect2: error: ld returned 1 exit status

$ # With inlining enabled, the program works as (possibly) expected:
$ gcc -std=c11 -O a2.c b2.c
$ gcc -std=c11 -O1 a2.c b2.c
$ ./a.out
foo called from main: return value = 4
foo called from g: return value = 3

如 IBM 文档所示,C++ 的规则是不同的。该程序不是有效的 C++,因为 foo 在两个翻译单元中的定义不同,但编译器没有义务检测此错误,并且适用通常的未定义行为规则(即,标准未定义什么将印)。碰巧的是,GCC 似乎显示了与 i7.1 相同的结果:

$ gcc -std=c++14 -x c++ a.c b.c
$ ./a.out
foo called from main: return value = 3, address = 0x55cd03df5670
foo called from g: return value = 3, address = 0x55cd03df5670