2011年6月2日 星期四

[C++] The significance about - extern "C"

extern "C" 是C++特有的組合關鍵字,在C裡並沒有這個的組合,僅有extern這個關鍵字!

為什麼C++會需要這樣的關鍵字組呢? 原因是C++它有一個複載(overloading)的功能,也就是說同樣的函式名稱可以有多個定義只要參數簽名不同即可。
比如說C++裡可以有以下的二個宣告


bar(int i, int j);

bar(double i, double j);

這二個函式都是同樣的名字叫foo,僅參數型式不同。然而在C語言裡是不被允許的! C++是如何處理這同名的函式呢? 其實他在編譯時會偷偷的把這二個函式名變成不同的名字,舉例來說bar(int i, int j)可能會被改成_bar_int_int(每種compiler產生不太一樣),而另一個則被改成_bar_double_double。這技術稱 Mangling。

問題來了! 當我們希望C++不要偷換函式名時該怎麼辦? 於是就有了extern "C" 這個關鍵字組出現了。這個字組就是請C++不要自己又偷天換日,請它保留原名。所以當我們宣告一個函式如下時:

extern "C" bar(int i, int j);

編譯器就不會把bar變成_bar_double_double。

實際使用的注意事項:

1/ 當C++使用C的函式庫(library)時,C++不能直接套用C的header檔。因為他會把header裡的宣告給mangleing了。所以他必須使用如下:

extern "C"
{
#include "C_LIB.h" //C_LIB 是由C語言所寫的。
}

2/ 相反的,在C語言的編譯器裡若要使用由C++所寫出來的C函式庫,那麼也不能直接的使用C++的header檔。因為此header檔必然存在 extern "C" 這個關鍵字組,而這字組C語言是不認識的。所以必需要把C++的header檔裡的extern "C" { } 移除後才可以讓C編譯器使用。

  • 注意事項


  • 一組多載函數中, 只能有一個函數被指明為 "extern C", 因為符號修飾後的關係, ex:

    void abc(int);
    "extern C" void abc(char);
    "extern C" void abc(float);
    

    使用 C 方式的修飾符號是 _函數名,因此是 _abc, 所以當第2組指明 "extern C" 也是 _abc 那麼兩組 C 函數的符號就重複了,此檢查在函數的多載就可以,但是同一個 scope 中函數沒有多載化就無法檢查出,更確切的說,"extern "C" 不受 namespace or class 修飾的影響, 因此同一編譯單元中只能有一個同名函數為 "extern C", 否則會有錯誤 ,ex:

    namespace N
    {
    extern "C" void abc(){} // _abc
    
    }
    
    extern "C" void abc(){} // _abc
    


    編譯時產生相同符號 _abc 函數定義故錯誤,由於 extern "C" 不受限於 namespace 的修飾因此將產生一個有趣現象也就是宣告和定義可以在不同的 namespace 出現。語法層面上的檢查就如同一般,而當語法檢查通過後產生符號時,extern "C" 的定義其符號不帶有 namespace 的修飾;同樣的,呼叫者其符號也不帶有 namespace 的修飾,ex:


    namespace Foo
    {
    extern "C" void abc() {} //有定義, 生成符號時, 不受限於 namespace Foo 修飾故符號為 _abc
    }
    
    namespace Bar
    {
    extern "C" void abc(); //純宣告 
    }
    
    int main()
    {
    Bar::abc(); //函數呼叫, 生成符號時, 不受限於 namespace Bar 修飾故符號為 _abc, 事實上鏈結到 ::Foo::abc()
    }
    

    還有下面如此鏈結錯誤的狀況:
    由於extern "C" 沒有多載功能,不論是 void abc() 或是 void abc(int a) 都將被以 _abc 來做為函式名稱,這是極度危險的.


    1.c
    extern "C" void abc();
    int main()
    {
    abc();
    }
    

    2.c
    #include
    extern "C" void abc(int a)
    {
    printf("%d",a);
    }
    

    當 1.c 和 2.c link 時可以,因為 c 沒有將參數名稱當作函式修飾名稱的一部分,故也就沒有多載,將 "C" 拿掉 link 時就會錯誤,因為會以 c++ 的方式來編譯。

    資料來源:C++中關於extern "C"的意義

    0 意見: