首頁  >  文章  >  php教程  >  C++中的extern “C”用法詳解

C++中的extern “C”用法詳解

高洛峰
高洛峰原創
2016-12-19 14:47:482005瀏覽

簡單來說,extern 「C」是C++宣告或定義C語言符號的方法,是為了與C相容。說來容易,要理解起來還是得費些周折,首先我們要從C++和C的差別說起。

符號

大家都知道,從程式碼到執行程式需要經過編譯和連結兩個過程,其中編譯階段會做語法檢測,程式碼展開,另外它還會做一件事,就是將變數轉成符號,連結的時候其實是透過符號來定位的。編譯器在編譯C和C++程式碼時,將變數轉成符號的過程是不同的。本文所使用的編譯器為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,我們執行編譯gcc -c hello.c得到目標檔hello.o,在Linux下用nm查看目標檔的符號表得到以下結果($符號代表shell命令提示字元)

$ nm hello.o  
0000000000000000 D g_prefix  
0000000000000000 T hello  
                 U printf

這是C程式碼編譯後的符號列表,其中第三列為編譯後的符號名,我們主要看自己定義的全域變數g_prefix和函數hello,它們的編譯後的符號名稱和程式碼裡的名字是一樣的。我們將hello.c重新命名為hello.cpp,重新編譯gcc -c hello.cpp得到hello.o,在用nm查看,結果如下

0000000000000000 T _Z5helloPKc  
                 U __gxx_personality_v0  
0000000000000000 D g_prefix  
                 U printf

這是C++程式碼編譯後的符號列表,gcc會自動根據文件後綴名來識別C和C++程式碼,這時我們發現g_prefix的符號沒變,但函數hello的符號變成了_Z5helloPKc,這就說明gcc在編譯C和C++程式碼時處理方式是不一樣的,對於C程式碼,變數的符號名稱就是變數本身(在早期編譯器會為C程式碼變數前加底線_,現在預設都不會了,在編譯時可以透過編譯選項-fno-leading-underscore和-fleading-underscore來明確設定),而對於C++程式碼,如果是資料變數且沒有嵌套,符號名也是本身,如果變數名稱有嵌套(在名稱空間或類別中)或是函數名,符號名就會依下列規則來處理


1、 符號以_Z開始
2、 如果有嵌套,後面緊跟N,然後是名稱空間、類別、函數的名字,名字前的數字是長度,以E結尾
3 、 如果沒嵌套,則直接是名字長度後面跟著名字
4、 最後是參數列表,類型和符號對應關係如下

    int    -> i  
    float  -> f  
    double -> d  
    char   -> c  
    void   -> v  
    const  -> K  
    *      -> P

這樣就很好理解為什麼C++程式碼裡的void hello(const char*)編譯之後符號為_Z5helloPKc(PKc翻譯成型別要從右到左翻譯為char const *,這是編譯器內部的表示方式,我們習慣的表示方式是const char*,兩者是一樣的),c++filt工具可以從符號反推名字,使用方法為c++filt _Z5helloPKc

這樣也很容易理解為什麼C++支援函數重載而C不支援了,因為C++將函數修飾為符號時把函數的參數型別加進去了,而C卻沒有,所以在C++下,即便函數名稱相同,只要參數不同,它們的符號名稱是不會衝突的。我們可以透過下面一個例子來驗證變數名和符號的這種關係。

/ * filename : test.cpp */  
#include <stdio.h>  
  
namespace myname  
{  
    int var = 42;  
}  
  
extern int _ZN6myname3varE;  
  
int main()  
{  
    printf("%d\n", _ZN6myname3varE);  
    return 0;  
}

這裡我們在名稱空間namespace定義了全域變數var,根據前面的內容,它會被修飾為符號_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語言的函數,而這些函數可能被C和C++程式碼調用,當我們提供給C++程式碼調用時,需要在頭檔裡加extern “C”,否則C++編譯的時候會找不到符號,而給C程式碼呼叫時又不能加extern “C”,因為C是不支援這樣的語法的,常見的處理方式是這樣的,我們以C的函式庫函數memset為例

#ifdef __cplusplus  
extern "C" {  
#endif  
  
void *memset(void*, int, size_t);  
  
#ifdef __cplusplus  
}  
#endif

其中__cplusplus是C++編譯器定義的一個宏,如果這份程式碼和C++一起編譯,那麼memset會在extern "C"裡被聲明,如果是和C程式碼一起編譯則直接聲明,由於__cplusplus沒有被定義,所以也不會有文法錯誤。這樣的技巧在系統頭檔裡常被用到。



更多C++中的extern 「C」用法詳解相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn