【问题标题】:extern "C" inline functionsextern "C" 内联函数
【发布时间】:2014-12-24 13:26:26
【问题描述】:

这段代码会导致未定义的行为吗?

header.h

#ifdef __cplusplus
extern "C"
{
#endif

inline int foo(int a)
{
    return a * 2;
}

#ifdef __cplusplus
}
#endif

def.c

#include "header.h"

extern inline int foo(int a);

use.c

#include "header.h"

int bar(int a)
{
    return foo(a + 3);
}

main.cpp

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

extern "C"
{
    int bar(int a);
}

int main(int argc, char** argv)
{
    printf("%d\n", foo(argc));
    printf("%d\n", bar(argc));
}

这是一个在 C 和 C++ 中都必须使用 inline 函数的程序示例。如果 def.c 被删除并且 foo 没有在 C 中使用,它会起作用吗? (这里假设 C 编译器是 C99。)

此代码在编译时有效:

gcc -std=c99 -pedantic -Wall -Wextra -c -o def.o def.c
g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c
g++ -std=c++11 -pedantic -Wall -Wextra -o extern_C_inline def.o main.o use.o

foo 仅在extern_C_inline 中出现一次,因为编译器在不同目标文件中输出的不同版本被合并,但我想知道这种行为是否由标准指定。如果我删除 fooextern 定义并使其成为 staticfoo 将多次出现在 extern_C_inline 中,因为编译器在每个编译单元中输出它。

【问题讨论】:

  • @BenVoigt:是的,不过语义略有不同。
  • @jxh 在 C 中,您需要在某处使用外部内联定义,以确保目标文件中存在非内联版本。见stackoverflow.com/questions/216510/extern-inline
  • @jxh 如果我将 foo 设为静态,那么它将多次出现在可执行文件中,这将增加可执行文件的大小。
  • @jxh "make it static" 通常不是一个好的解决方案,它会产生其他后果并改变程序的语义(例如,如果foo() 中有一个静态局部变量,则将@987654342 @static 意味着您在每个翻译单元中获得不同的静态局部变量,而不是整个程序的单个局部变量)。在有意义的地方使用static,而不仅仅是解决链接器错误。
  • 很有趣,这正是我不喜欢的地方。宏很烂。

标签: c++ c inline-functions


【解决方案1】:

程序在编写时有效,但需要def.c 以确保代码始终适用于所有编译器以及不同文件的任何优化级别组合。

因为上面有extern的声明,所以def.c提供了函数foo()的外部定义,可以用nm确认:

$ nm def.o
0000000000000000 T foo

无论该文件如何编译,该定义将始终存在于 def.o 中。

use.c 中有一个foo()内联定义,但根据C 标准中的6.7.4,未指定对foo() 的调用是使用该内联定义还是使用外部定义(实际上是否使用内联定义取决于文件是否经过优化)。如果编译器选择使用内联定义,它将起作用。如果它选择不使用内联定义(例如,因为它是在没有优化的情况下编译的),那么您需要在其他文件中进行外部定义。

没有优化use.o有一个未定义的引用:

$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c
$ nm use.o
0000000000000000 T bar
                 U foo

但通过优化它不会:

$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c -O3
$ nm use.o
0000000000000000 T bar

main.cpp 中会有foo() 的定义,但它通常会生成一个弱符号,因此如果在另一个对象中找到另一个定义,链接器可能不会保留它。如果弱符号存在,它可以满足use.o 中需要外部定义的任何可能引用,但如果编译器将foo() 内联到main.o 中,那么它可能不会在foo() 中发出任何foo() 定义,所以def.o中的定义仍然需要满足use.o

未经优化main.o包含弱符号:

$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp
$ nm main.o
                 U bar
0000000000000000 W foo
0000000000000000 T main
                 U printf

但是用-O3 编译main.cpp 会内联对foo 的调用,并且编译器不会为它发出任何符号:

$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp -O3
$ nm main.o
                 U bar
0000000000000000 T main
                 U printf

因此,如果foo() 不是use.o 中内联,但main.o 中内联,那么您需要def.o 中的外部定义

如果 def.c 被删除并且 foo 没有在 C 中使用,它会起作用吗?

是的。如果 foo 仅在 C++ 文件中使用,那么您不需要在 def.o 中对 foo 进行外部定义,因为 main.o 要么包含其自己的(弱)定义,要么将内联函数。 foo.o 中的定义只需要满足其他 C 代码对 foo 的非内联调用。


另外:C++ 编译器在优化main.o 时可以跳过为foo 生成任何符号,因为C++ 标准规定在一个翻译单元中声明inline 的函数必须在all 翻译单元,并且要调用声明为inline 的函数,该定义必须在与调用相同的文件中可用。这意味着编译器知道如果其他文件想要调用foo(),那么其他文件必须包含foo()的定义,因此当其他文件被编译时,编译器将能够根据需要生成函数的另一个弱符号定义(或内联它)。所以如果main.o中的所有调用都被内联了,就不需要在main.o中输出foo了。

这些是与 C 不同的语义,其中 use.c 中的内联定义可能会被编译器忽略,而 def.o 中的外部定义必须存在,即使 def.c 中没有任何内容调用它。

【讨论】:

  • 为什么仅仅回答 “没有 C 编译器参与其中” 是不够的,因为重点是 C 链接,而不是 C 编译器,甚至用extern "C" 编写的代码也会被C++ 编译器编译?
  • @user2485710,因为那不是真的。 def.cuse.c 由 C 编译器编译。
  • @JonathanWakely 当然可以,但是在一个以extern "C" 开头的问题中,这是一个仅限 C++ 的构造,这个东西将使用普通的C 编译器编译多少次?我认为这个问题受到以下事实的影响,即 OP 假设即使在 C++ 代码库中使用该代码,它也会由 C 编译器编译。
  • @user2485710 你认为#ifdef __cplusplus 检查是为了什么?我觉得你很困惑。 OP 正在清楚地使用 C 编译器编译 def.cuse.c因为问题中显示了,请参阅末尾的 gcc -std=c99 命令。跨度>
  • 感谢您的精彩回答!这肯定回答了我的问题。
猜你喜欢
  • 2020-02-15
  • 1970-01-01
  • 2014-12-21
  • 1970-01-01
  • 2017-01-05
  • 1970-01-01
  • 2011-08-23
  • 1970-01-01
相关资源
最近更新 更多