• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-167  評論-8  文章-0  trackbacks-0

            首先,一個編譯單元translation unit是指一個.cpp文件以及它所#include的所有.h文件,.h文件里的代碼將會被擴展到包含它的.cpp文件里,然后編譯器編譯該.cpp文件為一個.obj文件(假定我們的平臺是win32),后者擁有PEPortable Executablewindows可執行文件文件格式,并且本身包含的就已經是二進制碼,但是不一定能夠執行,因為并不保證其中一定有main函數。當編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器linker進行連接成為一個.exe文件。

             

            舉個例子:

             1//---------------test.h-------------------//
             2void f();//這里聲明一個函數f
             3 
             4//---------------test.cpp--------------//
             5#include”test.h”
             6void f()
             7{
             8//do something
             9}
              //這里實現出test.h中聲明的f函數
            10 
            11//---------------main.cpp--------------//
            12#include”test.h”
            13int main()
            14{
            15f(); //調用f,f具有外部連接類型
            16}

            17

            在這個例子中,test. cppmain.cpp各自被編譯成不同的.obj文件姑且命名為test.objmain.obj,在main.cpp中,調用了f函數,然而當編譯器編譯main.cpp時,它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個關于void f();的聲明,所以,編譯器將這里的f看作外部連接類型,即認為它的函數實現代碼在另一個.obj文件中,本例也就是test.obj,也就是說,main.obj中實際沒有關于f函數的哪怕一行二進制代碼,而這些代碼實際存在于test.cpp所編譯成的test.obj中。在main.obj中對f的調用只會生成一行call指令,像這樣:

             

            call f [C++中這個名字當然是經過mangling[處理]過的]

             

            在編譯時,這個call指令顯然是錯誤的,因為main.obj中并無一行f的實現代碼。那怎么辦呢?這就是連接器的任務,連接器負責在其它的.obj中(本例為test.obj尋找f的實現代碼,找到以后將call f這個指令的調用地址換成實際的f的函數進入點地址。需要注意的是:連接器實際上將工程里的.obj“連接成了一個.exe文件,而它最關鍵的任務就是上面說的,尋找一個外部連接符號在另一個.obj中的地址,然后替換原來的虛假地址。

             

            這個過程如果說的更深入就是:

             

            call f這行指令其實并不是這樣的,它實際上是所謂的stub,也就是一個jmp 0xABCDEF這個地址可能是任意的,然而關鍵是這個地址上有一行指令來進行真正的call f動作。也就是說,這個.obj文件里面所有對f的調用都jmp向同一個地址,在后者那兒才真正”call”f。這樣做的好處就是連接器修改地址時只要對后者的call XXX地址作改動就行了。但是,連接器是如何找到f的實際地址的呢在本例中這處于test.obj中),因為.obj.exe的格式是一樣的,在這樣的文件中有一個符號導入表和符號導出表import tableexport table其中將所有符號和它們的地址關聯起來。這樣連接器只要在test.obj的符號導出表中尋找符號f當然C++f作了mangling的地址就行了,然后作一些偏移量處理后因為是將兩個.obj文件合并,當然地址會有一定的偏移,這個連接器清楚寫入main.obj中的符號導入表中f所占有的那一項即可。

             

            這就是大概的過程。其中關鍵就是:

             

            編譯main.cpp時,編譯器不知道f的實現,所以當碰到對它的調用時只是給出一個指示,指示連接器應該為它尋找f的實現體。這也就是說main.obj中沒有關于f的任何一行二進制代碼。

             

            編譯test.cpp時,編譯器找到了f的實現。于是乎f的實現二進制代碼出現在test.obj里。

             

            連接時,連接器在test.obj中找到f的實現代碼二進制的地址通過符號導出表。然后將main.obj中懸而未決的call XXX地址改成f實際的地址。完成。

             

            然而,對于模板,你知道,模板函數的代碼其實并不能直接編譯成二進制代碼,其中要有一個實例化的過程。舉個例子:

             

             1//----------main.cpp------//
             2template<class T>
             3void f(T t)
             4{}
             5 
             6int main()
             7{
             8//do something
             9f(10); // call f<int> 編譯器在這里決定給f一個f<int>的實例
            10//do other thing
            11}

            12


            也就是說,如果你在main.cpp文件中沒有調用過ff也就得不到實例化,從而main.obj中也就沒有關于f的任意一行二進制代碼!如果你這樣調用了:

             

            f(10); // f<int>得以實例化出來

            f(10.0); // f<double>得以實例化出來

             

            這樣main.obj中也就有了f<int>f<double>兩個函數的二進制代碼段。以此類推。

             

            然而實例化要求編譯器知道模板的定義,不是嗎?

             

            看下面的例子(將模板的聲明和實現分離):

             

             1//-------------test.h----------------//
             2template<class T>
             3class A
             4{
             5public:
             6void f(); // 這里只是個聲明
             7}
            ;
             8 
             9//---------------test.cpp-------------//
            10#include”test.h”
            11template<class T>
            12void A<T>::f()  // 模板的實現
            13{
            14  …//do something
            15}

            16 
            17//---------------main.cpp---------------//
            18#include”test.h”
            19int main()
            20{
            21A<int> a;
            22f(); // #1
            23}

            24

             

            編譯器在#1處并不知道A<int>::f的定義,因為它不在test.h里面,于是編譯器只好寄希望于連接器,希望它能夠在其他.obj里面找到A<int>::f的實例,在本例中就是test.obj,然而,后者中真有A<int>::f的二進制代碼嗎?NO!!!因為C++標準明確表示,當一個模板不被用到的時侯它就不該被實例化出來test.cpp中用到了A<int>::f了嗎?沒有!!所以實際上test.cpp編譯出來的test.obj文件中關于A::f一行二進制代碼也沒有,于是連接器就傻眼了,只好給出一個連接錯誤。但是,如果在test.cpp中寫一個函數,其中調用A<int>::f,則編譯器會將其實例化出來,因為在這個點上(test.cpp中),編譯器知道模板的定義,所以能夠實例化,于是,test.obj的符號導出表中就有了A<int>::f這個符號的地址,于是連接器就能夠完成任務。

             

            關鍵是:在分離式編譯的環境下,編譯器編譯某一個.cpp文件時并不知道另一個.cpp文件的存在,也不會去查找當遇到未決符號時它會寄希望于連接器。這種模式在沒有模板的情況下運行良好,但遇到模板時就傻眼了,因為模板僅在需要的時候才會實例化出來,所以,當編譯器只看到模板的聲明時,它不能實例化該模板,只能創建一個具有外部連接的符號并期待連接器能夠將符號的地址決議出來。然而當實現該模板的.cpp文件中沒有用到模板的實例時,編譯器懶得去實例化,所以,整個工程的.obj中就找不到一行模板實例的二進制代碼,于是連接器也黔驢技窮了。

            因而,模板類的聲明與實現不能分開寫在cpp文件與h文件當中,除非你#include  xxx.cpp文件。


            posted on 2011-01-02 17:23 老馬驛站 閱讀(377) 評論(0)  編輯 收藏 引用 所屬分類: c++
            亚洲国产一成久久精品国产成人综合| 91亚洲国产成人久久精品网址| 精品久久久久久无码人妻蜜桃 | 久久久久国产一区二区| 久久久久久青草大香综合精品| 久久久久香蕉视频| 久久夜色精品国产噜噜麻豆| 国产精品久久久久久福利69堂| 久久国产热这里只有精品| 久久精品国产精品亚洲精品 | 久久w5ww成w人免费| 999久久久免费国产精品播放| 亚洲国产精品嫩草影院久久 | 一本综合久久国产二区| 久久精品aⅴ无码中文字字幕不卡| 久久精品国产一区二区| 99久久成人国产精品免费| 怡红院日本一道日本久久| 国产毛片欧美毛片久久久| 国产精品女同一区二区久久| 久久精品国产亚洲77777| 精品国产乱码久久久久软件| 精品久久久久久久久久久久久久久| 久久久久亚洲AV片无码下载蜜桃| 亚洲国产成人久久一区久久| 精品久久久久久无码中文字幕| 99久久人妻无码精品系列| 亚洲精品无码久久久久sm| 四虎国产精品成人免费久久| www亚洲欲色成人久久精品| 亚洲国产精品婷婷久久| 国产精品久久久久9999| 久久婷婷五月综合国产尤物app| 国产精品亚洲综合久久| 无码乱码观看精品久久| 久久综合给合综合久久| 久久精品女人天堂AV麻| 久久久精品国产亚洲成人满18免费网站 | 国产亚洲精久久久久久无码AV| 久久精品国产亚洲av麻豆小说| 青草国产精品久久久久久|