標簽:tar etc 數據讀取 那是 第一個 模塊 data 全局變量 i386
一、編譯及加載
C語言的編譯鏈接過程要把我們編寫的一個c程序(源代碼)轉換成可以在硬件上運行的程序(可執行代碼),需要進行編譯和鏈接。編譯就是把文本形式源代碼翻譯爲機器語言形式的目標文件的過程。鏈接是把目標文件、操作系統的啓動代碼和用到的庫文件進行組織形成最終生成可加載、可執行代碼的過程
程序運行時會將編譯好的文件從外存中加載到內存中,而後進行運行
過程圖解如下:
編譯過程又可以分成兩個階段:編譯和彙編。
編譯是指編譯器讀取源程序(字符流),對之進行詞法和語法的分析,將高級語言指令轉換爲功能等效的彙編代碼。
源文件的編譯過程包含兩個主要階段:
第一個阶段是預處理階段,在正式的編譯階段之前進行。預處理階段將根據已放置在文件中的預處理指令來修改源文件的內容。
主要是以下幾方面的處理:
頭文件的目的主要是爲了使某些定義可以供多個不同的C源程序使用,這涉及到頭文件的定位即搜索路徑問題。頭文件搜索規則如下:
第二個階段編譯、優化階段,編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間代碼表示或彙編代碼。
彙編實際上指彙編器(as)把彙編語言代碼翻譯成目標機器指令的過程。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。目標文件由段組成。通常一個目標文件中至少有兩個段:
静态库(static library)就是将相关的目标模塊打包形成的单独的文件。使用ar命令。
靜態庫的優點在于:
动态库(dynamic library)是一种特殊的目标模塊,它可以在运行时被加载到任意的内存地址,或者是与任意的程序进行链接。
動態庫的優點在于:
鏈接器主要是將有關的目標文件彼此相連接生成可加載、可執行的目標文件。鏈接器的核心工作就是符號表解析和重定位。
鏈接器將函數的代碼從其所在地(目標文件或靜態鏈接庫中)拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼。
爲創建可執行文件,鏈接器必須要完成的主要任務:
關于符號表和符號解析以及重定位的分析後續學習。
在此种方式下,函数的定义在动态链接库或共享对象的目标文件中。在编译的链接阶段,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器(ld-linux.so)链接程序在运行过程中根据记录的共享对象的符号定义来动态加载共享库,然后完成重定位。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
加载器把可执行文件从外存加载到内存并进行执行。 Linux中进程运行时的内存映像如下:
加載過程如下:
加载器首先创建如上图所示的内存映像,然后根据段头部表,把目标文件拷贝到内存的数据和代码段中。然后,加载器跳转到程序入口点(即符号_start 的地址),执行启动代码(startup code),启动代码的调用顺序如所示:
UNIX系统提供了一系列工具帮助理解和处理目标文件。GNUbinutils 包也提供了很多帮助。这些工具包括:
六、編譯文件詳解
一個簡單的程序被編譯成目標文件後的結構如下:
从图可以看出,已初始化的全局變量和局部静态变量保存在 .data段中,未初始化的全局變量和未初始化的局部静态变量保存在 .bss段中。
目標文件各個段在文件中的布局如下:
各個段介紹:
init段:
程序初始化入口代码,在main() 之前运行。
bss段:
BSS段属于静态内存分配。通常是指用来存放程序中未初始化的全局變量和未初始化的局部静态变量。未初始化的全局變量和未初始化的局部静态变量默认值是0,本来这些变量也可以放到data段的,但是因为他们都是0,所以为他们在data段分配空间并且存放数据0是没有必要的。
程序在運行時,才會給BSS段裏面的變量分配內存空間。
在目标文件(*.o)和可执行文件中,BSS段只是为未初始化的全局變量和未初始化的局部静态变量预留位置而已,它并没有内容,所以它不占据空间。
section table中保存了BSS段(未初始化的全局變量和未初始化的局部静态变量)内存空间大小总和。 (objdump -h *.o 命令可以看到)
data段:
数据段(datasegment)通常是指用来存放程序中已初始化的全局變量和已初始化的静态变量的一块内存区域。数据段属于静态内存分配。
text段:
代碼段(codesegment/textsegment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬于只讀,某些架構也允許代碼段爲可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。
rodata段:
存放的是只读数据,比如字符串常量,全局const变量 和 #define定义的常量。例如: char*p="123456", "123456"就存放在rodata段中。
strtab段:
存储的是变量名,函数名等。例如: char* szPath="/root",void func() 变量名szPath 和函数名func 存储在strtab段里。
shstrtab段:
bss,text,data等段名也存儲在這裏。
rel.text段:
针对 text段的重定位表,还有 rel.data(针对data段的重定位表)
heap堆:
堆是用于存放進程運行中被動態分配的內存段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)
stack棧:
是用戶存放程序臨時創建的局部變量,也就是說我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味著在數據段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,並且待到調用結束後,函數的返回值也會被存放回棧中。由于棧的先進先出特點,所以棧特別方便用來保存/恢複調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時數據的內存區。
驗證BSS內存空間
程序1:
int ar[30000];
void main()
{
......
}
程序2:
int ar[300000] = {1, 2, 3, 4, 5, 6 };
void main()
{
......
}
结论是:程序2编译之后所得的.exe文件比程序1的要大得多。 为什么?
區別很明顯,一個位于.bss段,而另一個位于.data段,兩者的區別在于:
l 全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;
l 而函数内的自动变量都在栈上分配空间。
l .bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);
l 而.data却需要占用,其内容由程序初始化,因此造成了上述情况。
注意:
1. bss段(未手动初始化的数据)并不给该段的数据分配空间. 程序运行后,系统分配内存空间并由系统初始化,默认内存空间的值都为0. section table中保存了BSS段(未初始化的全局變量和未初始化的局部静态变量)内存空间大小总和,所以程序运行后,系统知道该分配多少内存给BSS段。
2. data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。
这里有个疑问: data段是变量的内存空间,那是如何区分哪几个字节是变量a的? 哪几个字节是变量b的? 因为变量a,b的内存空间是在一起的,如果你不告诉他们的类型,我们的确是不知道变量a有几个字节,变量b有几个字节。
那么哪里保存了 变量a,b的类型了? 查资料发现,text代码段中调用a的汇编代码,是会告诉我们变量a的类型的,这样我们就知道读取哪几个字节的值了。 程序运行起来后,BSS段中变量内存數據讀取原理类似。
轉自:
https://blog.csdn.net/weixin_41042404/article/details/81239416
https://blog.csdn.net/sunny04/article/details/40627311
linux 目标文件(*.o) bss,data,text,rodata,堆,栈 以及程序加载运行理解(转)
標簽:tar etc 數據讀取 那是 第一個 模塊 data 全局變量 i386
原文地址:https://www.cnblogs.com/zl1991/p/15039949.html