天天看點

C++運作時候庫操作概述和整個程式運作流程

一、任何一個C/C++程式,它的背後都是一套龐大的代碼來進行支撐,以使得該程式能夠正常運作。這套代碼至少包括入口函數、及其依賴的函數所構成的函數集合。當然,它還應該包括各種标準函數(如字元串,數學運算等)的實。一般的程式運作過程如下:

1.作業系統建立程序後,把控制權交給程式的入口函數(gcc -e (_startEntryPoint)),這個函數往往是運作時庫的某個入口函數,glibc的入口函數是_start,msvc(vc6.0)是mainCRTStartup

2.入口函數對運作庫和程式運作環境進行初始化,包括堆、I/O、線程、全局變量構造函數(constructor)等。

3.調用MAIN函數,正式開始執行程式主體。

4.執行MAIN完畢,傳回入口函數,進行清理工作,包括全局變量析構、堆銷毀、關閉I/O等,然後進行系統調用結束程序

二、啟動時庫主要功能子產品

1.啟動與退出,包括入口函數及其依賴函數

2.标準函數

3.I/O功能的封裝和實作,如提供PRINT

4.堆的封裝和實作

5.調試支援等

三、程式詳細運作過程

以下分析預設為windows靜态連結過程

1.程式執行前裝載器會把使用者的參數和環境變量壓入棧,接着作業系統把控制權交給mainCRTStartup入口函數。

使用者的參數:對應int main(int argc, char **argv)

環境變量:系統公用資料,系統搜尋路徑等等

程式需要擷取使用者參數和環境變量均是從棧上擷取,需要了解棧幀的概念

2.初始化和OS版本相關的全局變量

3.初始化堆,每個程序都有屬于自己的堆。它是一次性從系統中申請一塊比較大的虛拟空間(實際需要時(如malloc)才會映射到實體頁),以後在程序中由庫的堆管理算法來維護這個堆。當堆不夠用時再繼續申請一塊大的虛拟空間繼續配置設定。可見,并非程式每次malloc都會調用系統API(API調用比較耗時,涉及到使用者态到核心态的上下文切換),效率比較高。

堆相關操作:

HeapCreate:建立一個堆,最終會調用virtualAlloc()系統API函數去建立堆。

HeapAlloc:malloc會調用該函數

HeapFree:free會調用該函數

HeapDestroy:摧毀一個堆

4.I/O初始化,繼承父程序打開檔案表。可見,子程序是可以通路父程序打開的檔案。如果父程序沒有打開标準的輸入輸出,該程序會初始化标準輸入輸出。即初始化一下指針變量:stdin,stdout,stderr。他們都是FILE類型指針。在linux和windows中,打開檔案對應于操作一個核心對象,其處于核心态,是以使用者态是不能直接操作該核心對象的。使用者隻能操作與核心對象相關聯的FILE結構指針。對應關系是:

C++運作時候庫操作概述和整個程式運作流程

printf其實是調用stdout指針在螢幕上輸出 #define printf(args...) fprintf(stdout, ##args) Args...表示變長輸入參數。用以下四個宏根據棧來擷取。Va_list,Va_start,Va_arg,Va_end

5.擷取指令行參數和環境變量

6.初始化C庫的一些資料

7.全局變量構造,如各個全局類對象的構造函數調用和标記__attribute__((constructor))屬性的各個函數。它們都應該在進入main前進行調用。

需要調用運作時庫和C/C++編譯器、連接配接器的配合才能實作這個功能

1)編譯器編譯某個.cpp(設為main.cpp)檔案時,會将所有的構造函數實作作為一個整體放到.init段,把析構函數實作放到.finit段,然後在,ctors段放置.init段的位址(該位址即是該檔案的各個構造函數的總入口)。

2)運作時庫有一個庫是crtbegin.o,它的.ctors段放置的内容為-1,ctrend.o,她的.ctors段放置的内容也是-1。

3)用連結器進行連接配接:ld crtbegin.o main.o crtend.o 一定要按這種順序,否則出錯。連結後的.ELF檔案是将以上各個檔案的.init/.finit/.ctors等段分别合并。當然.data/.text段也會相應合并。

全局變量構造時即是周遊.ctors段的内容,從-1(crtbegin.o)開始,再到-1(crtend.o)結束,中間每四個位元組即是各個檔案的構造入口函數指針,如果非0,即進行調用

8.注冊析構函數

為了支援C++類的析構函數,和标記__attribute__((destructor))屬性的各個函數在main之後會被調用,而且是按構造的相反順序進行調用的,同樣需要編譯器以及運作時庫的支援,原理跟構造相仿。隻是為了逆序,使用了atexit注冊各個函數,注冊時在連結清單頭插入連接配接,main退出以後也是從連結清單頭開始擷取連結清單函數,并進行調用

9.執行函數主體

調用main函數執行,等待傳回。在這裡可以用到之前已經初始化的各種資源,如I/O,堆,申請釋放等等

10.調用析構函數

11.釋放堆

12.釋放其它資源