此代码会导致未定义的行为吗?
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("%dn", foo(argc));
printf("%dn", bar(argc));
}
这是一个程序的例子,其中inline
函数必须在C和C++中使用。如果去掉def.c
而不在C中使用foo
,它会起作用吗?(这是假设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
中出现过一次,因为编译器在不同对象文件中输出的不同版本会被合并,但我想知道这种行为是否由标准指定。如果我删除foo
的extern
定义并使其成为static
,那么foo
将多次出现在extern_C_inline
中,因为编译器在每个编译单元中输出它。
程序在编写时是有效的,但需要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
中需要外部定义的任何可能的引用,但如果编译器在main.o
中内联foo()
,则它可能不会在main.o
中发出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,而在c中不使用foo,它会起作用吗?
是的。如果foo
仅在C++文件中使用,则不需要在def.o
中使用foo
的外部定义,因为main.o
要么包含自己的(弱)定义,要么将内联函数。foo.o
中的定义只需要满足其他C代码对foo
的非内联调用。
旁白:C++编译器在优化main.o
时可以跳过为foo
生成任何符号,因为C++标准规定,在一个翻译单元中声明为inline
的函数必须在所有翻译单元中内联声明,并且要调用声明为inline
的函数,定义必须与调用在同一文件中可用。这意味着编译器知道,如果其他文件想要调用foo()
,那么其他文件必须包含foo()
的定义,因此当编译其他文件时,编译器将能够根据需要生成函数的另一个弱符号定义(或内联它)。因此,如果main.o
中的所有调用都已内联,则无需在main.o
中输出foo
。
这些是与C不同的语义,在C中,编译器可能会忽略use.c
中的内联定义,并且def.o
中的外部定义必须存在,即使def.c
中没有调用它。