C/C++中的符号与extern C的作用

很多人都知道,在C++中调用C的api, 需要在C++中使用extern "C"来修饰。那么,为什么呢???

我们需要先来了解几个概念:

符号: 在链接中,我们将函数和变量统称为符号(Symbol), 函数名或变量名就是符号名(Symbol Name)

每个目标文件(使用gcc -c参数编译出来的.o文件)都会有一个相应的符号表(Symbol Table)。这个表里面记录了目标文件中所用到的所有符号。每个定义的符号都有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是他们的地址。

首先我们来了解下符号表。我们都知道gcc编译出来的可执行文件elf格式中有个段: text代码段、data数据段保存已初始化的全局变量、bss段保存未初始化的变量等等。其实还有其他段,我们可以用readelf命令来看下:

我们先写个简单的代码:

/*  file : lib.h  */
#ifndef __LIB_H__
#define __LIB_H__


void foobar();

#endif
#endif

 

/*  file: lib.c   */

#include 
#include 
#include 
#include 
#include "lib.h"

int number = 0;

void foobar()
{
	printf("PID:%d number: %d\n",getpid(),number);
	number++;
	printf("PID:%d number: %d\n",getpid(),number);
}

 

编译:

下面我们可以使用readelf -S来查看目标文件了。

C/C++中的符号与extern C的作用_第1张图片

好了,现在我们知道符号表里存着我们在代码里一些变量名和函数,我们还可以使用nm命令来查看具体的符号值。

C/C++中的符号与extern C的作用_第2张图片

 

接着,同学们可能会有疑问,讲了那么多符号的内容,可是跟我extern C有什么关系???逗我呢???

其实我们很接近答案了,我们都知道C++使用g++来编译,那我们就编译一下看看里面的符号表好了。

C/C++中的符号与extern C的作用_第3张图片

有没有发现?使用gcc或者g++编译的,生成的目标文件的符号不一样!!!

假如我们给 foobar 加上 extern "C"修饰:

#ifndef __LIB_H__
#define __LIB_H__
#ifdef __cplusplus
extern "C" {
#endif
	void foobar();
#ifdef __cplusplus
}
#endif
#endif

编译结果:

C/C++中的符号与extern C的作用_第4张图片

又变回我们熟悉的foobar了。需要特别注意的是,在g++编译时,会自动定义__cplusplus, 所以可以根据这个宏定义来判断是编译C还是C++.

 

其实上述内容涉及到符号修饰与函数签名。我们都知道在c++中拥有类、继承、重载、名称空间等特性,这些特性使得符号管理非常复杂。比如:两个名字相同但参数不同的函数foo(int )和foo(double ),按照C的特性是无法处理,会报编译错误信息。但在C++中,这两个函数签名并不相同,所以并不冲突。注意,函数签名不仅跟函数名有关系,还跟参数有关系,所以函数签名不一样。

在C++中,为了支持这些复杂的特性,人们发明了符号修饰(Name Decroation)的机制

例如在上述代码中,

void foobar()函数在C++中经过符号修饰的后的名称变为_Z6foobarv

 

具体符号修饰的规则就不在本节介绍了。我们要了解清楚extern C的作用就好了。extern C就是告诉编译器不要使用c++的符号修饰,这样生成的符号跟C还是一样的,就能在C++中链接C库了。

你可能感兴趣的:(程序员的自我修养)