C++中的extern “C”用法詳解
簡單來說,extern “C”是C++聲明或定義C語言符號的方法,是為了與C兼容。說來容易,要理解起來還是得費些周折,首先我們要從C++和C的區(qū)別說起。
符號
大家都知道,從代碼到可執(zhí)行程序需要經(jīng)過編譯和鏈接兩個過程,其中編譯階段會做語法檢測,代碼展開,另外它還會做一件事,就是將變量轉(zhuǎn)成符號,鏈接的時候其實是通過符號來定位的。編譯器在編譯C和C++代碼時,將變量轉(zhuǎn)成符號的過程是不同的。本文所使用的編譯器為gcc4.4.7
我們先來看一段簡單的代碼
/* hello.c */
#include <stdio.h>
const char* g_prefix = "hello ";
void hello(const char* name)
{
printf("%s%s", g_prefix, name);
}
注意,這里的文件名為hello.c,我們執(zhí)行編譯gcc -c hello.c得到目標文件hello.o,在Linux下用nm查看目標文件的符號表得到如下結(jié)果($符號代表shell命令提示符)
$ nm hello.o
0000000000000000 D g_prefix
0000000000000000 T hello
U printf
這是C代碼編譯后的符號列表,其中第三列為編譯后的符號名,我們主要看自己定義的全局變量g_prefix和函數(shù)hello,它們的編譯后的符號名和代碼里的名字是一樣的。我們將hello.c重命名為hello.cpp,重新編譯gcc -c hello.cpp得到hello.o,在用nm查看,結(jié)果如下
0000000000000000 T _Z5helloPKc
U __gxx_personality_v0
0000000000000000 D g_prefix
U printf
這是C++代碼編譯后的符號列表,gcc會自動根據(jù)文件后綴名來識別C和C++代碼,這時我們發(fā)現(xiàn)g_prefix的符號沒變,但函數(shù)hello的符號變成了_Z5helloPKc,這就說明gcc在編譯C和C++代碼時處理方式是不一樣的,對于C代碼,變量的符號名就是變量本身(在早期編譯器會為C代碼變量前加下劃線_,現(xiàn)在默認都不會了,在編譯時可以通過編譯選項-fno-leading-underscore和-fleading-underscore來顯式設(shè)置),而對于C++代碼,如果是數(shù)據(jù)變量并且沒有嵌套,符號名也是本身,如果變量名有嵌套(在名稱空間或類里)或者是函數(shù)名,符號名就會按如下規(guī)則來處理
1、 符號以_Z開始
2、 如果有嵌套,后面緊跟N,然后是名稱空間、類、函數(shù)的名字,名字前的數(shù)字是長度,以E結(jié)尾
3、 如果沒嵌套,則直接是名字長度后面跟著名字
4、 最后是參數(shù)列表,類型和符號對應(yīng)關(guān)系如下
int -> i
float -> f
double -> d
char -> c
void -> v
const -> K
* -> P
這樣就很好理解為什么C++代碼里的void hello(const char*)編譯之后符號為_Z5helloPKc(PKc翻譯成類型要從右到左翻譯為char const *,這是編譯器內(nèi)部的表示方式,我們習(xí)慣的表示方式是const char*,兩者是一樣的),c++filt工具可以從符號反推名字,使用方法為c++filt _Z5helloPKc
下面列舉幾個函數(shù)和符號的對應(yīng)例子
這樣也很容易理解為什么C++支持函數(shù)重載而C不支持了,因為C++將函數(shù)修飾為符號時把函數(shù)的參數(shù)類型加進去了,而C卻沒有,所以在C++下,即便函數(shù)名相同,只要參數(shù)不同,它們的符號名是不會沖突的。我們可以通過下面一個例子來驗證變量名和符號的這種關(guān)系。
/ * filename : test.cpp */
#include <stdio.h>
namespace myname
{
int var = 42;
}
extern int _ZN6myname3varE;
int main()
{
printf("%d\n", _ZN6myname3varE);
return 0;
}
這里我們在名稱空間namespace定義了全局變量var,根據(jù)前面的內(nèi)容,它會被修飾為符號_ZN6myname3varE,然后我們手動聲明了外部變量_ZN6myname3varE并將其打印出來。編譯并運行,它的值正好就是var的值
$ gcc test.cpp -o test -lstdc++
$ ./test
42
extern "C"
有了符號的概念我們再來看extern “C”的用法就很容易了
extern "C"
{
int func(int);
int var;
}
它的意思就是告訴編譯器將extern “C”后面的括號里的代碼當做C代碼來處理,當然我們也可以以單條語句來聲明
extern "C" int func(int);
extern "C" int var;
這樣就聲明了C類型的func和var。很多時候我們寫一個頭文件聲明了一些C語言的函數(shù),而這些函數(shù)可能被C和C++代碼調(diào)用,當我們提供給C++代碼調(diào)用時,需要在頭文件里加extern “C”,否則C++編譯的時候會找不到符號,而給C代碼調(diào)用時又不能加extern “C”,因為C是不支持這樣的語法的,常見的處理方式是這樣的,我們以C的庫函數(shù)memset為例
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void*, int, size_t);
#ifdef __cplusplus
}
#endif
其中__cplusplus是C++編譯器定義的一個宏,如果這份代碼和C++一起編譯,那么memset會在extern "C"里被聲明,如果是和C代碼一起編譯則直接聲明,由于__cplusplus沒有被定義,所以也不會有語法錯誤。這樣的技巧在系統(tǒng)頭文件里經(jīng)常被用到。
相關(guān)文章
C語言pow()函數(shù)實現(xiàn)求x的y次方的值
這篇文章主要介紹了C語言pow()函數(shù)實現(xiàn)求x的y次方的值,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C++數(shù)組模擬之單鏈表與雙鏈表和棧和隊列的實現(xiàn)過程
這篇文章主要介紹了C++數(shù)組模擬之單鏈表與雙鏈表和棧和隊列的實現(xiàn)過程,了解內(nèi)部原理是為了幫助我們做擴展,同時也是驗證了一個人的學(xué)習(xí)能力,如果你想讓自己的職業(yè)道路更上一層樓,這些底層的東西你是必須要會的,跟隨下文來具體了解吧2023-02-02C++數(shù)據(jù)結(jié)構(gòu)之鏈表的創(chuàng)建
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之鏈表的創(chuàng)建的相關(guān)資料,希望通過本文幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10