程式員的自我修養 – 符號修飾 函數簽名 以及一個引申的問題: extern “c”

來源:互聯網
上載者:User

由於最近都在忙著複習考試,所以自己的讀書筆記也就落下了. 現在只剩下最後的單片機了,還有四天時間,現在複習?

算啦,等到最後一天再說吧. ~~額  慢慢的我已經習慣這節奏,喜歡上裸考的心跳~

 

關於目標檔案的相關知識,其實還是蠻多的.我能做到的就是選取自己需要的知識去學習.(其實是自己能理解和在時間

不浪費的情況下去選取值得學習的知識,從大體上去掌握.這可能和研究這些編譯連結的細節的最初出發點是相違背的,

最初的出發點是想要弄清楚細節,但是從另外一個角度來思考,重點是編譯和連結,對於這其中的小細節,忽略也是

可以接受的).

 

一、如何引申出符號修飾與函數簽名的概念?

小生有仔細的思考過這個問題,但是最後還是選擇總結書上的語言,然後引申出這個問題,能力所限,如果您讀到這篇

拙文,又不滿意的地方請拍磚,小生在這裡接受批評教育.  

 

1.符號修飾與函數簽名誕生的背景和條件:

   20世紀70年代,編譯器編譯原始碼產生目標檔案時,符號與相應的變數和函數的名字是一樣的.後來Unix平台和C語言

   發明時,已經存在了相當多的使用彙編編寫的庫和目標檔案.這樣問題就產生了,一個C程式要使用這些庫的話,C語言

   中不可以使用這些庫中定義的函數和變數的名字作為符號名,否則將會跟現有的目標檔案衝突.

 

   為了防止類似的符號名衝突,Unix下的C語言就規定,C語言原始碼中的所有全域變數和函數經過編譯後,相對應的符號名

   前加上底線"_".而Fortran語言的原始碼經過編譯以後,所有的符號前後都加上"_".

   

    這種簡單而原始的方法的確能夠暫時減少多種語言目標檔案之間的符號衝突的機率,但是沒有從根本上去解決符號衝突的問題.

    a. 對不同語言原始碼編譯後產生的目標檔案中添加上不同的符號約束,但是同一種語言的符號衝突問題還是沒有解決.

    b. 由於模組是協同開發的原因,導致出現的符號衝突機率更大. 如果想要減少衝突,那麼就要制定相當複雜和繁瑣的編碼規範.

        而且這些工作在現在看來是無謂的,浪費時間.

  就上面這一點,我們是可以驗證的.gcc 提供了編譯參數"-fleading-underscore"/"-fno-leading-underscore"來開啟或者關閉產生下

  劃線符號.一個簡單的HelloWorld.c程式:

#include <stdio.h>#include <stdlib.h>/// GLOBAL TEST VARint global_test_var = 100;/// TEST METHODvoid test_method();int main(int argc, char *argv[]){  test_method();   printf ("%s\n","HelloWorld");    return EXIT_SUCCESS;}void test_method(){  printf ("%s\n","TEST METHOD!");  return;}

 使用工具readelf可以查看產生的目標檔案中的資訊.

 可以看到確實是是在全域變數和函數方法前面產生了底線.

2. 名稱修飾和函數簽名的誕生和C++關係密切:

   很多人都知道C++很複雜,擁有很多複雜的機制和特性,有的時候我們會深深的愛上這些機制和特性,有的時候也會讓我

   們為之頭痛.C++有類,繼承,虛機制,重載,名稱空間等等這些特性,這些特性使符號管理更加複雜.所以後來誕生了符號

   修飾和函數簽名.至於其中的相關細節,我想這些沒必要討論,其原理是很簡單的:

         使用名稱修飾和函數簽名就是為了使避免產生符號衝突.使用一定的機制使原始碼中的符號在產生的目標檔案中能夠保持

   唯一性,保證了唯一性也就削弱和消除了衝突的可能性.是不是很簡單呢?呵呵,至少原理是很簡單的,具體的實現機制應該

   是相當複雜的,這裡不做猜測.

 

  前面使用readelf工具可以查看目標檔案中的內容資訊,以及編譯後編譯器對符號做的變動.那就去驗證一下名稱修飾和函數

  簽名的機制.同樣還是一個簡單的程式:

 

#include <iostream>namespace sample_namespace{  int sample_var = 10;   typedef int sample_int;    class sample_class  {

sample_class(simple_int parm):sample_parm(parm){}
int sample_func(int sample_parm1, double sample_parm2); sample_int sample_parm; };}int main(int argc, char *argv[]){ return 0;}

 

 這個小程式確實是很簡單,下面使用工具來看看產生的目標檔案中的資訊:

 (囧了 發現看不到什麼東西...) 不怕,還有其他的工具使用,我們使用objdump.

 

這裡看到在.text節區中_GLOBAL__sub_I__ZN16sample_namespace10sample_varE這個東西,好吧,先不管這是什麼東西,下面看看

彙編代碼:

 

 好吧,這裡就比較的清晰了,下面就來解釋一下這個東西: _ZN16sample_namespace10sample_varE

 這是名稱修飾機制產生的.這樣子看起來是不清晰的,不過是可以使用工具去解釋的: c++filt _ZN16sample_namespace10sample_varE

 結果很簡單  其實就是我們的sample_namespace:sample_var.

 

三、extern "C"

 其實extern "C" 是一個關鍵字,是C++提供相容C的關鍵字.

 C++編譯器會將在extern "C"大括弧範圍內的代碼都做為C語言處理. 所以相應C++的名稱修飾和函數簽名機制都不會有作用.

 在很多的IDE中,在建立一個源檔案的時候都會友好的做好一些基礎工作,C與C++相容的一些代碼也會自動產生:

 

#ifdef _cplusplusextern "C" {#endif// xxxx#ifdef _cpluspus}#endif

 

 這些技巧的使用在很多的庫中都有用到,很常見.

 既然使用關鍵字extern "C"修飾的語句在執行的時候都不會經過C++名稱修飾的處理,那麼假設:

   我們瞭解C++名稱修飾的機制.

 那麼我們可以在代碼中嘗試加入經過名稱修飾處理並且使用extern "C"修飾的語句,那麼會怎麼樣子處理呢?

 表述能力有限,舉個例子:

就像上面的例子一樣,改寫一下部分代碼:

extern "C" int _ZN16sample_namespace10sample_varE;int main(int argc, char *argv[]){  using namespace std;  cout<<_ZN16sample_namespace10sample_varE<<endl;      return 0;}

 這種結果會怎麼樣呢? 在處理的過程中並沒有引用命名空間sample_namespace,但是卻嘗試著去輸出其中的變數simple_var.

 結果是成功了,成功輸出了:10.

 

 到這裡就盡我所能的表述完了,總結了一下自己的看書以及查資料的所得. 主要目的是作為自己的總結. 如果您看到這篇文章,

 並且能獲得一點點收穫,小生會很高興.如果您覺得寫的不好,那麼就請原諒~~

 

 

 

  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.