為什麼C++編譯器不能支援對模板的分離式編譯 (轉)
為什麼C++不能支援對模板的分離式編譯
--ppLiu
首先,C++標準中提到,一個編譯單元[translation unit]是指一個.cpp以及它所include的所有.h檔案,.h檔案裡的程式碼將會被擴充套件到包含它的.cpp檔案裡,然後編譯器編譯該.cpp檔案為一個.obj檔案,後者擁有PE[Portable Executable,即可檔案]檔案格式,並且本身包含的就已經是二進位制碼,但是,不一定能夠執行,因為並不保證其中一定有main。當編譯器將一個工程裡的所有.cpp檔案以分離的方式編譯完畢後,再由聯結器(linker)進行連線成為一個.exe檔案。
舉個例子:
//---------------test.h-------------------//
void f();//這裡宣告一個函式f
//---------------test.cpp--------------//
#include”test.h”
void f()
{
…//do something
} //這裡實現出test.h中宣告的f函式
//---------------main.cpp--------------//
#include”test.h”
int main()
{
f(); //f,f具有外部連線型別
}
在這個例子中,test. cpp和main.cpp各被編譯成為不同的.obj檔案[姑且命名為test.obj和main.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 0x23423[這個地址可能是任意的,然而關鍵是這個地址上有一行指令來進行真正的call f動作。也就是說,這個.obj檔案裡面所有對f的呼叫都jmp向同一個地址,在後者那兒才真正”call”f。這樣做的好處就是聯結器修改地址時只要對後者的call XXX地址作改動就行了。但是,聯結器是如何找到f的實際地址的呢[在本例中這處於test.obj中],因為.obj於.exe的格式都是一樣的,在這樣的檔案中有一個符號匯入表和符號匯出表[import table和export 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實際的地址。
完成。
:namespace prefix = o ns = "urn:schemas--com::office" />
然而,對於模板,你知道,模板函式的程式碼其實並不能直接編譯成二進位制程式碼,其中要有一個“具現化”的過程。舉個例子:
//----------main.cpp------//
template
void f(T t)
{}
int main()
{
…//do something
f(10); //call f
…//do other thing
}
也就是說,如果你在main.cpp檔案中沒有呼叫過f,f也就得不到具現,從而main.obj中也就沒有關於f的任意一行二進位制程式碼!!如果你這樣呼叫了:
f(10); //f
f(10.0); //f
這樣main.obj中也就有了f
然而具現化要求編譯器知道模板的定義,不是嗎?
看下面的例子:[將模板和它的實現分離]
//-------------test.h----------------//
template
class A
{
public:
void f(); //這裡只是個宣告
};
//---------------test.cpp-------------//
#include”test.h”
template
void A
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A
a. f(); //編譯器在這裡並不知道A
//於是編譯器只好寄希望於聯結器,希望它能夠在其他.obj裡面找到
//A
//二進位制程式碼嗎?NO!!!因為C++標準明確表示,當一個模板不被用到的時
//侯它就不該被具現出來,test.cpp中用到了A
//際上test.cpp編譯出來的test.obj檔案中關於A::f的一行二進位制程式碼也沒有
//於是聯結器就傻眼了,只好給出一個連線錯誤
//但是,如果在test.cpp中寫一個函式,其中呼叫A
//址,於是聯結器就能夠完成任務。
}
關鍵是:在分離式編譯的環境下,編譯器編譯某一個.cpp檔案時並不知道另一個.cpp檔案的存在,也不會去查詢[當遇到未決符號時它會寄希望於聯結器]。這種在沒有模板的情況下執行良好,但遇到模板時就傻眼了,因為模板僅在需要的時候才會具現化出來,所以,當編譯器只看到模板的宣告時,它不能具現化該模板,只能建立一個具有外部連線的符號並期待聯結器能夠將符號的地址決議出來。然而當實現該模板的.cpp檔案中沒有用到模板的具現體時,編譯器懶得去具現,所以,整個工程的.obj中就找不到一行模板具現體的二進位制程式碼,於是聯結器也傻眼了!!!!!!!!!!!!!!!!!!!!!!!!!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-962857/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 模板引數,模板分離編譯編譯
- 模板函式編譯原理函式編譯原理
- c++模板類的使用,編譯的問題C++編譯
- PHP編譯器BPC 6.0釋出,支援namespace,支援closure,成功編譯 workermanPHP編譯namespace
- C++編譯器優化C++編譯優化
- C++編譯器認為的指標型別(靜態聯編)C++編譯指標型別
- Vue 模板編譯原理Vue編譯原理
- 將C++編譯為Flash可用的swcC++編譯
- 為什麼編譯原理被稱為龍書?編譯原理
- 編譯器的自展和自舉、交叉編譯編譯
- 前端面試-模板編譯前端面試編譯
- 關於支援OPenACC的編譯器說明編譯
- VS設定 LLVM-Clang 編譯器進行編譯C++專案LVM編譯C++
- 都有Python了,還要什麼編譯器!Python編譯
- [譯] 為什麼 UX 和 UI 需要分離開?UXUI
- 淺談彙編器、編譯器和直譯器編譯
- 安裝c, c++編譯器 on AIXC++編譯AI
- 【譯】為什麼ReasonReact是編寫React的最佳方式React
- C++ 編譯過程C++編譯
- 對預編譯的理解編譯
- 程式碼線上編譯器(上)- 編輯及編譯編譯
- [譯] 為什麼我用 JavaScript 來編寫 CSSJavaScriptCSS
- vue模板編譯(原理篇)Vue編譯
- vue編譯器Vue編譯
- CUDAFORTRAN編譯器編譯
- [譯] 使 WebAssembly 更快:Firefox 的新流式和分層編譯器WebFirefox編譯
- Vue3 模板編譯原理 (Vue 的編譯模組整體邏輯)Vue編譯原理
- Go編譯器簡介【譯】Go編譯
- 編譯原理——C++版桌面計算器編譯原理C++
- 編譯 TensorFlow 的 C/C++ 介面編譯C++
- protobuf 的交叉編譯使用(C++)編譯C++
- GraphJin:GraphQL自動編譯轉為SQL編譯SQL
- 嵌入式—編譯器背後的故事編譯
- Java條件編譯是什麼?Java編譯
- [譯]編寫可以複用的 HTML 模板HTML
- C++物件模型:編譯分析C++物件模型編譯
- QT支援https及編譯OpenSSLQTHTTP編譯
- Java編譯與反編譯Java編譯
- 一文搞懂C/C++常用編譯器C++編譯