剖析extern "C"

env:
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)


在c++中调用c语言写的模块时经常在头文件中加入这样一段预处理命令:

#ifdef __cplusplus
extern "C" {   
#endif
/*some c header or declaration*/
#ifdef __cplusplus
}
#endif

之所以要引入extern "C"(注: 引号中是大写的C), 是因为c++中存在overload即函数重载, 为了区别函数名相同参数不同的函数, c++编译器在编译期间会将函数名和函数参数作为整体进行编译来得到链接所需的符号;c语言中没有overload, 编译器直接以函数名作为链接符号, 无视函数名后面的参数。


请看以下例程:

例程一:

有以下2个模块main.cc和add.c, 在main.cc中调用add.c中的函数add

/*main.cc模块*/
#include 

//extern "C"
//{
  extern int add(int a, int b);
//}

int main(int argc, char * argv[])
{
  int s;
  
  s = add(1, 2); 
  std::cout << "s = " << s << std::endl;  

  return 0;
}
/*add.c模块*/
int add(int a, int b)
{
  return a + b;
}

执行以下命令:
g++ -c main.cc
gcc -c add.c
g++ main.o add.o -o test
则会报链接错误:
undefined reference to `add(int , int)', 意为找不到该函数定义。
执行objdump -t main.o查看符号表发现main.cc中调用的外部函数add已被编译为"00000000 UND 00000000 _Z3addii"(add后面的2个i为参数int, int 的缩写), 明显函数编译后的链接符号与函数参数是有关系的。再执行objdump -t add.o查看符号表发现add函数对应的符号就是add。显而易见链接器肯定找不到链接符号为_Z3addii的函数, 因为我们从来没有定义过。

试想: 其他模块不动将add.c中的add函数改名为_Z3addii是否可以链接成功呢? 答案是肯定的, 但是这么写代码有啥意义呢?

例程二:

有以下2个模块main.cc和add.c, 在main.cc中调用add.c中的函数add
将例程一中extern "C"的注释打开, 如下所示:

/*main.cc模块*/
#include 

extern "C"
{
  extern int add(int a, int b);
}

int main(int argc, char * argv[])
{
  int s;
  
  s = add(1, 2);
  std::cout << "s = " << s << std::endl;  

  return 0;
}
/*add.c模块*/
int add(int a, int b)
{
  return a + b;
}

执行以下命令:
g++ -c main.cc
gcc -c add.c
g++ main.o add.o -o test
编译链接正常, 执行可执行文件./test, 即可看到我们想要的结果:s = 3。此刻我们再次执行命令objdump -t main.o看到main.cc中的调用的外部函数add被编译为:"UND 00000000 add", 符号和函数名完全一致而没有添加一些奇怪的东西!

总结:

  extern "C"就是要告诉c++编译器大括号里面声明的函数要按照c语言的方式编译成链接需要的符号。这样c++在链接的时候才能找到对应的c函数。

你可能感兴趣的:(剖析extern "C")