QT/C++中extern “C”的作用

來源:互聯網
上載者:User
extern "C"的作用

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)" (?add@@YAHHH@Z),原因是找不到add的目標模組 


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


例如 


int add(int , int)==>add@@YAHHH@Z, 


float add(float , float )==>add@@YAMMM@Z, 


以上是VC6的命名方式,不同的編譯器會不同,總之不同的參數同樣的函數名將編譯成不同目標名,以便於函數重載是調用具體的函數. 


編譯cpp.cpp中編譯器在cpp檔案中發現add(1, 0);的調用而函式宣告為extern int add(int x, int y);編譯器就決定去找add@@YAHHH@Z,可惜他找不到,因為C的源檔案把extern int add(int x, int y);編譯成_add了; 


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


#ifndef __H 

#define __H 

#ifdef __cplusplus 

extern "C" { 

#endif 


extern 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 __cplusplus 

extern "C" { 

#endif 


extern int add(int, int); 


#ifdef __cplusplus 



#endif 


#endif /* _C_H_ */ 


這樣編譯 


/*-----------c.c--------------*/ 

int add(int x, int y){ 

return x+y; 





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

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


而編譯c++源檔案 


/*-----------cpp.cpp--------------*/ 

#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)而不是add@@YAHHH@Z; 


這也就為什麼DLL中常看見extern "C" {},windows是採用C語言編製他首先要考慮到C可以正確調用這些DLL,而使用者可能會使用C++而extern "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++編譯器就會將這個名字變成相應的修飾名,比如:?f@@YAXHD@Z. 

然而,c編譯器編譯的庫的內建函式名(連接器使用)是完全不同的.這樣,當c++連接器串連c的函數庫時,將會產生內部使用函數不匹配. 

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

extern後跟一個字串來指定想聲明的函數的連線類型,後面是函式宣告,比如: 

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

這樣,就是告訴編譯器是c串連,這樣就不會轉換函式名了.此例中,編譯後的內建函式名是_f.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.