天天看點

extern "C" 用法 (轉貼)

前些天,程式設計式是用到了很久以前寫的C程式,想把裡面的函數利用起來,連接配接發現出現了找不到具體函數的錯誤:

以下是假設舊的C程式庫

C的頭檔案

/*-----------c.h--------------*/#ifndef _C_H_#define _C_H_extern int add(int x, int y);#endif      

C的源檔案

/*-----------c.c--------------*/int add(int x, int y){	return x+y;}      

C++的調用

/*-----------cpp.cpp--------------*/#include "c.h"void main(){	add(1, 0);}      

這樣編譯會産生錯誤cpp.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" ([email protected]@[email protected]),原因是找不到add的目标子產品

這才令我想起C++重載的函數命名方式和C函數的命名方式,讓我們回顧一下:C中函數編譯後命名會在函數名前加以"_",比如add函數編譯成obj檔案時的實際命名為_add,而c++命名則不同,為了實作函數重載同樣的函數名add因參數的不同會被編譯成不同的名字

例如

int add(int , int)==>[email protected]@[email protected],

float add(float , float )==>[email protected]@[email protected],

以上是VC6的命名方式,不同的編譯器會不同,總之不同的參數同樣的函數名将編譯成不同目标名,以便于函數重載是調用具體的函數。

編譯cpp.cpp中編譯器在cpp檔案中發現add(1, 0);的調用而函數聲明為extern int add(int x, int y);編譯器就決定去找[email protected]@[email protected],可惜他找不到,因為C的源檔案把extern int add(int x, int y);編譯成_add了;

為了解決這個問題C++采用了extern "C",這就是我們的主題,想要利用以前的C程式庫,那麼你就要學會它,我們可以看以下标準頭檔案你會發現,很多頭檔案都有以下的結構

#ifndef __H#define __H#ifdef __cplusplusextern "C" {#endifextern int f1(int, int);extern int f2(int, int);extern int f3(int, int);	#ifdef __cplusplus}#endif#endif /*__H*/      

如果我們仿制該頭檔案可以得到

#ifndef _C_H_#define _C_H_#ifdef __cplusplusextern "C" {#endifextern int add(int, int);#ifdef __cplusplus}#endif#endif /* _C_H_ */       

這樣編譯

int add(int x, int y){

return x+y;

}

這時源檔案為*.c,__cplusplus沒有被定義,extern "C" {}這時沒有生效對于C他看到隻是extern int add(int, int);

add函數編譯成_add(int, int);

而編譯c++源檔案

#include "c.h"

void main()

{

add(1, 0);

}

這時源檔案為*.cpp,__cplusplus被定義,對于C++他看到的是extern "C" {extern int add(int, int);}編譯器就會知道 add(1, 0);調用的C風格的函數,就會知道去c.obj中找_add(int, int)而不是[email protected]@[email protected];

這也就為什麼DLL中常看見extern "C" {},windows是采用C語言編制他首先要考慮到C可以正确調用這些DLL,而使用者可能會使用C++而extern "C" {}就會發生作用

一、修飾名(Decorated Name)

C/C++程式中的函數在内部是通過修飾名來辨別的。修飾名是在函數定義或原型編譯階段由編譯器建立字元串。當你在LINK等工具中要指定一個函數名時,會用到修飾名。

1、使用修飾名:

大多數情況下,你不必知道函數的修飾名是什麼。連接配接器等工具通常都能處理函數未修飾的名字。然而,在有些情況下,你可能需要指定函數的修飾名。對于C++重載函數和特定的成員函數(如:構造函數和析構函數),你必須指定這些函數的修飾名,以便連接配接器等工具能夠比對名字。同時,你也必須在那些引用c或c++函數名的彙編源檔案中使用修飾名。

2、檢視修飾名:

如果你編譯了一個源檔案,該源檔案中包含了函數定義或原型,你可以獲得函數的修飾名形式。

(1)用編譯器清單(compiler listing)來檢視:

   (i)通過将清單檔案類型編譯器選項(/FA[c|s]) 設定為下面中的一種,來産生清單檔案:Assembly with Machine Code (/FAc); Assembly with Source Code (/FAs); Assembly, Machine Code, and Source (/FAcs).

   (ii)在産生的清單檔案中,找到包含未經修飾的函數定義的行。

   (iii)查找前面一行。PROC NEAR 指令标簽前就是函數名經過修飾後的形式。

(2)使用DUMPBIN工具來檢視:

  在.OBJ或.LIB上運作 DUMPBIN,使用/SYMBOLS選項。在輸出中查找未經修飾的函數定義。後面跟着的就是經過修飾的函數名,用圓括号括起來的。

二、替代連接配接說明:

如果在c++中編寫一個程式需要用到c的庫,那該如何?如果這樣聲明一個c函數:

void f(int a,char b);

c++編譯器就會将這個名字變成相應的修飾名,比如:[email protected]@[email protected]。

然而,c編譯器編譯的庫的内部函數名(連接配接器使用)是完全不同的。這樣,當c++連接配接器連接配接c的函數庫時,将會産生内部使用函數不比對。

故,c++中提供了一個替代連接配接說明(alternate linkage specification),它是通過重載extern關鍵字來實作的。

extern後跟一個字元串來指定想聲明的函數的連接配接類型,後面是函數聲明,比如:

 extern "C" void f(int a,char b);

這樣,就是告訴編譯器是c連接配接,這樣就不會轉換函數名了。此例中,編譯後的内部函數名是_f。