要使内联函数定义在外部链接,是否需要使用“ extern”?

时间:2020-06-15 16:41:59

标签: c gcc language-lawyer inline

GCC可以将以下.c文件编译并将其链接为可执行文件:

main.c

#include <stdio.h>
#include "addsub.h"

int main(void)
{
    printf("%d %d\n", add(1, 2), sub(1, 2));
    return 0;
}

addsub.c

#include "addsub.h"

inline int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return add(a, -b);
}

addsub.h

#ifndef ADDSUB_H__
#define ADDSUB_H__

int add(int, int);
int sub(int, int);

#endif

根据 C11 6.7.4 Functon说明符,第7段:

[...] 对于具有外部链接的函数,存在以下限制:如果使用 inline 函数说明符声明了函数,则它也应在同一翻译单元中定义。如果翻译单元中某个函数的所有文件范围声明都包含 inline 而没有 extern 的函数说明符,则该翻译中的定义单位是内联定义。内联定义不为函数提供外部定义,也不禁止在另一个翻译单元中使用外部定义。 [...]

上面的任何函数声明中都没有使用extern关键字,因此GCC在“ addsub.c”中提供add函数的外部定义时正确吗?

6.2.2标识符链接中,第5段说:

如果函数标识符的声明中没有存储类说明符,则其链接的确定与使用存储类说明符 extern 声明时完全相同。 [...]

但是,当省略add关键字时,似乎没有理由向内联extern函数添加外部链接。

2 个答案:

答案 0 :(得分:2)

是的,GCC运行正常。但是,您没有指示它添加任何自定义的外部定义。

关键是要了解什么是“外部定义”。

6.9外部定义

4如5.1.1.1所述,程序文本后面的单位 预处理是翻译单元,由一系列序列组成 外部声明。这些被称为“外部”,因为 它们出现在任何功能之外(因此具有文件作用域)。如 在6.7中讨论过,该声明也导致存储 保留给由标识符命名的对象或函数的对象是 定义。

5外部定义是外部声明,也是 函数的定义(内联定义除外)或 目的。如果在外部链接中使用通过外部链接声明的标识符 表达式(不是作为sizeof或_Alignof的操作数的一部分 运算符,其结果是一个整数常量),在整个位置上 程序中应该只有一个外部定义 标识符否则,最多只能有一个。

或多或少,外部定义是在文件范围内定义的任何内容。理解此行为的关键在于仅用inline声明符声明的函数不提供外部定义。这大致意味着翻译单元不拥有该功能的符号。因此,如果程序仅包含函数的内联定义,则链接可能会失败。

为什么在您的情况下它不会失败?这是因为addsub.c包含addsub.h。该翻译单元的外观是

int add(int, int);
int sub(int, int);

inline int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return add(a, -b);
}

顶部add的外部声明使“内联定义”也成为外部定义。在您引用的段落中

如果翻译中函数的所有文件范围声明 单位包括没有extern的内联函数说明符,则 该翻译单元中的定义是内联定义。

很明显,并非每个函数声明都只包含inline说明符(在这种情况下为第一个声明),因此addsub.c中的定义实际上是一个外部定义。

答案 1 :(得分:1)

不满足条件“如果翻译单元中某个函数的所有文件范围声明都包含inline函数说明符而不包含extern”,因为addsub.c包含addsub.h,其中包含int add(int, int);,这是add的声明,其中不包含inline函数说明符。