extern 「C」 的含義及解決的問題

2021-06-06 10:56:21 字數 4158 閱讀 3108

c

與c++程式連線問題

它們之間的連線問題主要是因為c c++編繹器對函式名解碼的方式不能所引起的,考慮下面兩個函式

/* c*/ 

int strlen(char* string) 

//c++ 

int strlen(char* string) 

兩個函式完全一樣。在c在函式是通過函式名來識別的,而在c++中,由於存在函式的過載問題,函式的識別方式通函式名,函式的返回型別,函式引數列表三者組合來完成的。因此上面兩個相同的函式,經過c,c++編繹後會產生完全不同的名字。所以,如果把乙個用c編繹器編繹的目標**和乙個用c++編繹器編繹的目標**進行連線,就會出現連線失敗的錯誤。

解決的方法是使用extern c,避免c++編繹器按照c++的方式去編繹c函式

在標頭檔案中定義:

extern "c" int strlen(char* string) 

或extern "c" 

當c編繹器遇到extern "c"的時候就用傳統的c函式編譯方法對該函式進行編譯。由於c編繹器不認識extern "c"這個編繹指令,而程式設計師又希望c,c++程式能共用這個標頭檔案,因此通常在標頭檔案中使用_cplusplus巨集進行區分:

#if define _cplusplus 

extern "c" 

#endif 概述

extern "c" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是「extern」的;其次,被它修飾的目標是「c」的。讓我們來詳細解讀這兩重含義。

(1) 被extern "c"限定的函式或變數是extern型別的

extern是c/c++語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。記住,下列語句:

extern int a;

僅僅是乙個變數的宣告,其並不是在定義變數a,並未為a分配記憶體空間。變數a在所有模組中作為一種全域性變數只能被定義一次,否則會出現連線錯誤。

通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。例如,如果模組b欲引用該模組a中定義的全域性變數和函式時只需包含模組a的標頭檔案即可。這樣,模組b中呼叫模組a中的函式時,在編譯階段,模組b雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組a編譯生成的目標**中找到此函式。

與extern對應的關鍵字是static,被它修飾的全域性變數和函式只能在本模組中使用。因此,乙個函式或變數只可能被本模組使用時,其不可能被extern 「c」修飾。

(2) 被extern "c"修飾的變數和函式是按照c語言方式編譯和連線的

編譯方式

未加extern 「c」宣告時的編譯方式

首先看看c++中對類似c的函式是怎樣編譯的。

作為一種物件導向的語言,c++支援函式過載,而過程式語言c則不支援。函式被c++編譯後在符號庫中的名字與c語言的不同。例如,假設某個函式的原型為:

void foo( int x, int y );

該函式被c編譯器編譯後在符號庫中的名字為_foo,而c++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為「mangledname」)。_foo_int_int這樣的名字包含了函式名、函式引數數量及型別資訊,c++就是靠這種機制來實現函式過載的。例如,在c++中,函式void foo( int x, int y )與void foo( int x, float y)編譯生成的符號是不相同的,後者為_foo_int_float。

同樣地,c++中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了乙個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。

未加extern "c"宣告時的連線方式

假設在c++中,模組a的標頭檔案如下:

// 模組a標頭檔案 modulea.h

#ifndef module_a_h

#define module_a_h

int foo( int x, int y );

#endif

在模組b中引用該函式:

// 模組b實現檔案 moduleb.cpp

#include "modulea.h"

foo(2,3);

實際上,在連線階段,聯結器會從模組a生成的目標檔案modulea.obj中尋找_foo_int_int這樣的符號!

加extern "c"宣告後的編譯和連線方式

加extern "c"宣告後,模組a的標頭檔案變為:

// 模組a標頭檔案 modulea.h

#ifndef module_a_h

#define module_a_h

extern "c" int foo( int x, int y );

#endif

在模組b的實現檔案中仍然呼叫foo( 2,3 ),其結果是:

(1)模組a編譯生成foo的目標**時,沒有對其名字進行特殊處理,採用了c語言的方式;

(2)聯結器在為模組b的目標**尋找foo(2,3)呼叫時,尋找的是未經修改的符號名_foo。

如果在模組a中函式宣告了foo為extern "c"型別,而模組b中包含的是extern int foo( int x, int y ) ,則模組b找不到模組a中的函式;反之亦然。

所以,可以用一句話概括extern「c」這個宣告的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,**於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):

實現c++與c及其它語言的混合程式設計。

明白了c++中extern "c"的設立動機,我們下面來具體分析extern "c"通常的使用技巧。

習慣用法

extern "c"的慣用法

(1)在c++中引用c語言中的函式和變數,在包含c語言標頭檔案(假設為cexample.h)時,需進行下列處理:

extern "c"

而在c語言的標頭檔案中,對其外部函式只能指定為extern型別,c語言中不支援extern "c"宣告,在.c檔案中包含了extern "c"時會出現編譯語法錯誤。

筆者編寫的c++引用c函式例子工程中包含的三個檔案的源**如下:

/* c語言標頭檔案:cexample.h */

#ifndef c_example_h

#define c_example_h

extern int add(int x,int y);

#endif

/* c語言實現檔案:cexample.c */

#include "cexample.h"

int add( int x, int y )

// c++實現檔案,呼叫add:cppfile.cpp

extern "c"

int main(int argc, char* argv)

如果c++呼叫乙個c語言編寫的.dll時,當包括.dll的標頭檔案或宣告介面函式時,應加extern "c" 。

(2)在c中引用c++語言中的函式和變數時,c++的標頭檔案需新增extern "c",但是在c語言中不能直接引用宣告了extern "c"的該標頭檔案,應該僅將c檔案中將c++中定義的extern "c"函式宣告為extern型別。

筆者編寫的c引用c++函式例子工程中包含的三個檔案的源**如下:

//c++標頭檔案 cppexample.h

#ifndef cpp_example_h

#define cpp_example_h

extern "c" int add( int x, int y );

#endif

//c++實現檔案 cppexample.cpp

#include "cppexample.h"

int add( int x, int y )

/* c實現檔案 cfile.c

/* 這樣會編譯出錯:#include "cppexample.h" */

extern int add( int x, int y );

int main( int argc, char* argv )

總結 c和c++對函式的處理方式是不同的.extern "c"是使c能夠呼叫c++寫的庫檔案的乙個手段,如果要對編譯器提示使用c的方式來處理函式的話,那麼就要使用extern "c"來說明。

POST DELETE PUT GET的含義及區別

post delete put get就像對應著資料庫的crud 增 刪 改 查 使用標準 post uri 建立 delete uri 刪除 put uri 更新或建立 get uri 查詢 get請求,用於向伺服器傳送查詢資料的請求,只是查詢,不會增加 修改資料,不會影響伺服器上資源的內容。無論...

解決golang gin框架跨域及註解的問題

在golang的路上緩慢前進 gin框架 跨域問題的解說與方法 路由初始化 var engine gin.default func main 跨域 func cors gin.handlerfunc headers程式設計客棧tr strings.join headerkeys,if headers...

迭代的含義及操作

基本 迭代是重複反饋過程的活動,其目的通常是為了接近並到達所需的目標或結果。每一次對過程的重複被稱為一次 迭代 而每一次迭代得到的結果會被用來作為下一次迭代的初始值。計算機 在電腦科學中,迭代是程式中對一組指令 或一定步驟 的重複。它既可以被用作通用的術語 與 重複 同義 也可以用來描述一種特定形式...