編譯器做了些什麼 圖1:編譯器通常分割成幾個更小的程式 靜态連結與動态連結 圖2:靜态連結與動态連結的差別 動态連結的優勢: 1, 動态連結可執行檔案比功能相同的靜态連結的可執行檔案的體積小。它能夠節省磁盤空間和虛拟記憶體,因為函數庫隻有在需要時才被映射到程序中。以前,避免把函數庫的拷貝綁定到每個可執行程式檔案的惟一方法就是把伺服器置于核心而不是函數庫中,使得核心膨脹。 2, 所有動态連結到某個特定函數庫的可執行檔案在運作時共享該函數庫的一個單獨拷貝。作業系統核心保證映射到記憶體中的含資料庫可以被使用它的其他程序共享,提供了更好的I/O和交換空間使用率,節省了實體記憶體,進而提高了系統的整體性能。如果可執行檔案是靜态連結的,每個檔案都将擁有一份函數庫的拷貝,顯然極為浪費空間。 關于函數連結 1, 動态庫檔案的擴充名是.so,而靜态庫檔案的擴充名是.a,并以libname的形式命名。 2, gcc通過-lthread選項,告訴編譯器連結libthread.so。 3, 編譯器期望在确定的目錄尋找庫。預設查找一些特定目錄如/usr/lib, /lib,-Lpathname告訴編譯器在其他位置查找庫。 4, 通過觀察頭檔案,确認使用的函數庫。 如math.h-->/usr/lib/libm.so, thread.h—>/usr/lib/libthread.so 運作時資料結構 圖3:源檔案到可執行檔案的映射 圖4:可執行檔案核心映像 圖5:共享庫的虛拟位址空間 圖6: 過程活動記錄 C語言自動提供的服務之一就是跟蹤調用鍊,即哪些函數調用了哪些函數,當下一個return語句執行後,控制将傳回何處等。C語言通過堆棧中的過程活動記錄解決這一問題,當每個函數被調用時,都會産生一個過程活動記錄(或類似的結構)。過程活動記錄是一種資料結構,用于支援過程調用,并記錄調用結束後傳回調用點所需要的全部資訊,如圖6所示。活動記錄内容的描述很具有說明性,結構的具體細節在不同的編譯器中各不相同。過程活動記錄并不一定在堆棧中,有時為了提高效率,将過程活動記錄置于寄存器中。 從堆棧實作函數調用的方式可以解釋為什麼不能從函數中傳回一個指向該函數局部自動變量的指針。 setjmp 和 longjmp setjmp和longjmp通過操縱過程活動記錄實作的,部分彌補了C語言有限的轉移能力,這兩個函數必須協同工作。 1, setjmp(jmp_buf j)必須首先調用。它表示“使用j記錄現在的位置。函數傳回零。” 2, longjmp(jmp_buf j, int i)可以接着被調用。它表示“回到j所記錄的位置,讓它看上去像是從原先的setjmp函數傳回的一樣。但是函數傳回i,使代碼能夠知道它是實際上時通過longjmp傳回的。 3, 當使用于longjmp()時,j的内容被銷毀。 semjmp儲存一份程式的計數器和目前棧頂指針,也可以根據需要儲存一些初始值。longjmp恢複這些值,有效的轉移控制并把狀态重置回到儲存狀态的時候,即“展開堆棧”。 與goto的差別: 1, goto語句不能跳出C語言目前函數,而longjmp可以跳得更“遠”。 2, longjmp隻能跳回曾經到過的地方,在這個“到過的地方”執行setjmp記錄當時的狀态資訊。 longjmp接受一個額外的整型參數并傳回它的值,這可以知道是由longjmp轉移到這裡的還是從上一條語句執行後自然而然的到這裡的。 #include<stdio.h> #include <setjmp.h> jmp_buf ebuf; void f2(void); int main(void) { int i; printf("1"); i=setjmp(ebuf); if(i==0) { f2(); printf("This will not be printed."); } printf("%d",i); getchar(); return 0; } void f2(void) { printf("2"); |