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 <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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來查看目標文件了。

好了,現在我們知道符號表裏存着我們在代碼裏一些變量名和函數,我們還可以使用nm命令來查看具體的符號值。

 

接着,同學們可能會有疑問,講了那麼多符號的內容,可是跟我extern C有什麼關係???逗我呢???

其實我們很接近答案了,我們都知道C++使用g++來編譯,那我們就編譯一下看看裏面的符號表好了。

有沒有發現?使用gcc或者g++編譯的,生成的目標文件的符號不一樣!!!

假如我們給 foobar 加上 extern "C"修飾:

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

編譯結果:

又變回我們熟悉的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庫了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章