淺析C和C++函數的互相援用。本站提示廣大學習愛好者:(淺析C和C++函數的互相援用)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析C和C++函數的互相援用正文
1.引言
C++說話的創立初志是“a better C”,然則這其實不意味著C++中相似C說話的全局變量和函數所采取的編譯和銜接方法與C說話完整雷同。作為一種欲與C兼容的說話,C++保存了一部門進程 式說話的特色(被眾人稱為“不完全空中向對象”),因此它可以界說不屬於任何類的全局變量和函數。然則,C++究竟是一種面向對象的法式設計說話,為了支 持函數的重載,C++對全局函數的處置方法與C有顯著的分歧。
2.從尺度頭文件說起
某企業已經給出以下的一道面試題:為何尺度頭文件都有相似以下的構造?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
明顯,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的感化是避免該頭文件被反復援用。那末
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的感化又是甚麼呢?我們將鄙人文逐個道來。
3.深層揭密extern "C"
extern "C" 包括兩重寄義,從字面上便可獲得:起首,被它潤飾的目的是“extern”的;其次,被它潤飾的目的是“C”的。讓我們來具體解讀這兩重寄義。
被extern "C"限制的函數或變量是extern類型的;
extern是C/C++說話中注解函數和全局變量感化規模(可見性)的症結字,該症結字告知編譯器,其聲明的函數和變量可以在本模塊或其它模塊中應用。
記住,以下語句:
extern int a;
僅僅是一個變量的聲明,其其實不是在界說變量a,並未為a分派內存空間。變量a在一切模塊中作為一種全局變量只能被界說一次,不然會湧現銜接毛病。
平日,在模塊的頭文件中對本模塊供給給其它模塊援用的函數和全局變量以症結字extern聲明。例如,假如模塊B欲援用該模塊A中界說的全局變量和函數 時只需包括模塊A的頭文件便可。如許,模塊B中挪用模塊A中的函數時,在編譯階段,模塊B固然找不到該函數,然則其實不會報錯;它會在銜接階段中從模塊A編 譯生成的目的代碼中找到此函數。
與extern對應的症結字是static,被它潤飾的全局變量和函數只能在本模塊中應用。是以,一個函數或變量只能夠被本模塊應用時,其弗成能被extern “C”潤飾。
被extern "C"潤飾的變量和函數是依照C說話方法編譯和銜接的;
未加extern “C”聲明時的編譯方法
起首看看C++中對相似C的函數是如何編譯的。
作為一種面向對象的說話,C++支撐函數重載,而進程式說話C則不支撐。函數被C++編譯後在符號庫中的名字與C說話的分歧。
例如,假定某個函數的原型為:
void foo( int x, int y );
該函數被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會發生像_foo_int_int之類的名字(分歧的編譯器能夠生成的名字分歧,然則都采取了雷同的機制,生成的新名字稱為“mangled name”)。
_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"平日的應用技能。
4.extern "C"的習用法
(1)在C++中援用C說話中的函數和變量,在包括C說話頭文件(假定為cExample.h)時,需停止以下處置:
extern "C"
{
#include "cExample.h"
}
而在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 )
{
return x + y;
}
// c++完成文件,挪用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
假如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 )
{
return x + y;
}
/* C完成文件 cFile.c
/* 如許會編譯失足:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}