標籤:使用 資料 問題 代碼 時間 line sp 程式
調用函數實際上將程式執行順序轉移到函數所存放在記憶體中某個地址,將函數的程式內容執行完後,再返回到轉去執行該函數前的地方。這種轉移操作要求在轉去前要保護現場並記憶執行的地址,轉回後先要恢複現場,並按原來儲存地址繼續執行。
當程式運行到某個節點時,如果發現有函數調用語句(函數要事先聲明或者事先定義),程式就自動儲存當前程式啟動並執行各個參數、變數到堆棧中去,然後利用所給函數參數調用該函數並運行得出結果,並且將結果儲存到記憶體寄存器中,然後程式自動返回函數調用前的節點處,再將堆棧中的資料出棧,然後再繼續運行後面的程式!(其實質就是壓棧然後再出棧的操作。當程式檢測到有函數調用語句時,自動將組合語言的中斷程式複製到相應的程式碼片段並執行)
因此,函數調用要有一定的時間和空間方面的開銷,於是將影響其效率。特別是對於一些函數體代碼不是很大,但又頻繁地被調用的函數來講,解決其效率問題更為重要。引入內嵌函式實際上就是為瞭解決這一問題。
在程式編譯時間,編譯器將程式中出現的內嵌函式的調用運算式用內嵌函式的函數體來進行替換。顯然,這種做法不會產生轉去轉回的問題,但是由於在編譯時間函數體中的代碼被替代到程式中,因此會增加目標程式碼量,進而增加空間開銷,而在時間開銷上不象函數調用時那麼大,可見它是以目標代碼的增加為代價來換取時間的節省。
在程式中,調用內嵌函式時,該函數在編譯時間被替代,而不是像一般函數那樣是在運行時被調用。
內嵌函式的基本思想在於將每個函數調用以它的代碼體來替換,這種做法很可能會增加整個目標代碼的體積。在一台記憶體有限的電腦裡,過分地使用內聯所產生的程式會因為有太大的體積而導致可用空間不夠。即使可以使用虛擬記憶體,內聯造成的代碼膨脹也可能會導致不合理的頁面調度行為(系統顛簸),這將使你的程式運行慢得象在爬。過多的內聯還會降低指令快取的命中率,從而使取指令的速度降低,因為從主存取指令當然比從緩衝要慢。
另一方面,如果內嵌函式體非常短,編譯器為這個函數體產生的程式碼就會真的比為函數調用產生的程式碼要小許多。如果是這種情況,內聯這個函數將會確實帶來更小的目標代碼和更高的快取命中率!
要牢記在心的一條是,inline指令就象register,它只是對編譯器的一種提示,而不是命令。也就是說,只要編譯器願意,它就可以隨意地忽略掉你的指令,事實上編譯器常常會這麼做。例如,大多數編譯器拒絕內聯"複雜"的函數(例如,包含迴圈和遞迴的函數);還有,即使是最簡單的虛函數調用,編譯器的內聯處理常式對它也愛莫能助。(這一點也不奇怪。virtual的意思是"等到運行時再決定調用哪個函數",inline的意思是"在編譯期間將調用之處用被調函數來代替",如果編譯器甚至還不知道哪個函數將被調用,當然就不能責怪它拒絕產生內聯調用了)。以上可以歸結為:一個給定的內嵌函式是否真的被內聯取決於所用的編譯器的具體實現。
假設寫了某個函數f並聲明為inline,如果出於什麼原因,編譯器決定不對它內聯,那將會發生些什麼呢?最明顯的一個回答是將f作為一個非內嵌函式來處理:為f產生代碼時就象它是一個普通的"外聯"函數一樣, 對f的調用也象對普通函數調用那樣進行。