先說結論 : extern "C"隻影響到鏈接期的name mangling 什麼是name mangling? 請看 : C++函數重載的實現機制之name mangling - 知乎 (zhihu.com) 舉個例子 : // external.h #ifdef __cplusplus exte ...
先說結論 : extern "C"隻影響到鏈接期的name mangling
什麼是name mangling?
請看 : C++函數重載的實現機制之name mangling - 知乎 (zhihu.com)
舉個例子 :
// external.h #ifdef __cplusplus extern "C" { #endif void external(); #ifdef __cplusplus } #endif
// external.cc #include "external.h" template <typename T> // 這明顯是C++特性, gcc是無法編譯的 void external() { T a; }
// main.c #include "external.h" int main() { external(); }
在這裡 : main函數裡面調用了c++函數external(), 如果這個函數沒被 extern "C"包裹, 會出現"undefined reference to"的錯誤.
這是因為g++、clang++都優先判斷尾碼名 ( 除非自己改option ), 就會對external.cc文件進行C++方式編譯 :
由於c++重載機制的存在, 編譯器只能在你命名之後 再悄悄重新給函數做個標記, 這個標記名也是有規則的, 比如g++, [ _Z + 函數名長度 + v / i ...標記符 ] .
external()函數被compiler賦予一個"符號", 如果是gcc, 這個符號就是external;
如果是g++, 這個符號是 _Z8externalv 或者_Z8externali;
而clang++更誇張, 變成了"?external@@YAHXZ"或者"?external@@YAHH@Z"這種怪物.
然後把obj文件中定義和引用的global variable和function存到一張"符號表"中方便鏈接器的工作.
------------------------------------------------------------------------------------------------------------------
名字 | 類型 | 是否可被外部引用 | 區域
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_Z8externalv | 定義, 引用 | 可 | 代碼段
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_Z8externali | 定義, 引用 | 可 | 代碼段
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
main | 定義 | |
-------------------------------------------------------------------------------------------------------------------
鏈接器說"我開始找了 ! ", 然後給main函數找external(), 發現沒有c版本的, 只有c++版本的呀, 它幹不了了, "undefined reference to external".
如果你把聲明extern "C", 鏈接器才能找到c版本的external(), 加上依賴庫/模塊 全打包起來. 還是拿gnu來說吧, c程式會打包libgcc.a靜態庫,c++程式則會打包很多libstdc++.a的東西和一些各式各樣的依賴, 這也是一般c++程式大小都要pure c大的原因, 在嵌入式平臺這種差距尤為明顯.
人家卡馬喬也有話說的 : " 我找的是什麼表啊, 你這個表什麼都有 我都能找到為什麼不能鏈接? "
c文件和c++文件直接鏈接在一起, 鏈接器應該用libgcc這個鏈子還是libstdc++呢?它沒法去判斷啊,只能你騙他一下這是c文件, 用libgcc去鏈.
所以, 你extern "C"包裹函數的聲明, 並不會改變函數體內語句的實際編譯方式, 因為它的定義仍在.cc文件里, g++仍會優先將它以c++的方式編譯. 但是函數名的確是以c的方式編去了.