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

时间:2018-07-26 07:16:55

标签: c linkage inline-functions

按照理论,inline函数在C中具有内部/静态链接,也就是说,它们仅在单个翻译单元中可见。 因此,在两个单独的文件中定义的内联函数应该不能互相看到,并且两个都有自己的地址空间。

我正在尝试以下文件

***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

上述编译错误反映了 bc 能够在 ac 中看到foo的定义,并且编译失败(对于内部链接的内联函数)。

请帮助我了解我是否缺少某些东西。

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

2 个答案:

答案 0 :(得分:3)

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


从C99开始,没有声明链接的函数声明不会生成函数对象。内联定义将仅与内联替换一起使用,并且编译器没有义务执行此优化。期望函数的外部定义存在于其他转换单元中,并且如果使用函数对象(通过获取其地址或在编译器选择不执行该函数的上下文中调用该对象),则该定义必须存在。内联替换。

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

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

在C99(及更新的版本)中,没有链接规范的内联函数只是内联定义(“内联定义不为该函数提供外部定义,并且不禁止在另一个翻译单元中使用外部定义。 ”§6.7.4p7)。但是在GCC扩展中,没有链接规范的内联函数被赋予了外部链接(就像非内联函数声明一样)。然后,GCC对extern inline进行特殊大小写,以表示“不生成函数对象”,这实际上与标准C99对没有externstatic修饰符的内联函数的处理相同。请参见GCC manual,尤其是最后一部分。

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

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

$ 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

答案 1 :(得分:0)

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