源程序.cpp 預處理得到
預處理文件.i 編譯得到
匯編文件.S 匯編得到
目標文件.o 鏈接得到
可執行文件
例子:main.cpp fun.cpp fun.h
1 #include <iostream> 2 #include "fun.h" 3 using namespace std; 4 5 #define PI 3.14 6 7 int main() 8 { 9 print(); 10 cout<<PI<<endl; 11 return 0; 12 }
1 #ifndef _FUN_H_ 2 #define _FUN_H_ 3 void print(); 4 #endif
1 #include <iostream> 2 #include "fun.h" 3 4 void print() 5 { 6 std::cout<<"hello,world"<<std::endl; 7 }
1. 預處理
g++ -E main.cpp -o main.i
main.i、fun.i:
對源程序其中的偽指令(以#開頭的指令)和特殊符號進行處理
(1)宏定義指令
如 main.cpp中有 #define PI 3.14,預處理之后進行了替換
(2)條件編譯指令
#ifdef、#ifndef、#else、#elif、#endif等,根據宏定義決定對哪些代碼進行處理,避免重復的引用
(3)頭文件包含指令
#include <xx.h> #include "xx.h"等
這些頭文件中有大量的宏定義
(4)特殊符號
1 printf("Date:%s,Time:%s,File:%s,Line:%d,Func:%s\n",__DATE__,__TIME__,__FILE__,__LINE__,__FUNCTION__);
經過預處理,得到的.i文件沒有宏定義、沒有條件編譯指令、沒有特殊符號
2. 編譯
g++ -S main.i -o main.S
預處理之后的文件只有一些數字、字符串及關鍵字的定義,經過g++編譯程序:詞法分析、語法分析、優化,生成匯編文件
3. 匯編
匯編代碼匯編成機器指令
4. 鏈接
多個.o文件以及庫文件鏈接成可執行文件
ld 一堆庫文件 fun.o main.o -o a.out
必要的庫可通過 g++ -v main.o 查看
g++ 最終通過調用 collect2來鏈接文件,collect2是對ld的封裝
(1)靜態鏈接
以一組可重定位目標文件和命令行參數作為輸入,生成一個完全鏈接的可以加載和運行的可執行目標文件。
將鏈接庫的代碼復制到可執行程序中
靜態鏈接做的事:
①符號解析:將目標文件符號引用和定義聯系起來(因為某些符號是引用其他模塊的符號)
②重定位:編譯器、匯編器生成從地址0開始的代碼和數據,鏈接器把每個符號定義和一個存儲器位置聯系起來,然后修改所有對這些符號的引用,使得從另一個位置開始執行。
(2)動態鏈接
函數的定義在動態鏈接庫或共享對象的目標文件中,在鏈接階段,動態鏈接庫只提供符號表等少量信息保證所有符號引用都有定義(不像靜態鏈接直接復制過去),保證編譯順利通過。在可執行文件執行時,動態連接庫將函數等內容映射到運行時相應進程的虛地址空間。
(3)目標文件
①可重定位目標文件:含二進制代碼、數據,因引用了其他模塊的符號而不能執行
②共享目標文件/動態庫: .so文件
③可執行文件
(4)目標文件的格式 ELF文件
ELF頭:描述文件系統字長、字節序、ELF頭大小、目標文件類型、目標機類型等
.text:代碼段,可執行二進制機器指令
.rodata:只讀數據段,存常量如字符串等
.data:數據段,以明確初始化的全局數據(全局變量、靜態變量),是靜態內存分配
.bss:塊存儲段,未被明確初始化的全局數據,這些全局數據會初始化為0,是靜態內存分配
上面的四個段會加載到內存中
.symtab:符號表,定義和引用的函數和全局變量
.rel.text:代碼段需要重定位的信息,存儲需要靠重定位修改位置的符號的匯總
.rel.data:數據段需要重定位的信息
.debug:gcc -g選項會生成此段
.line:源程序的行號映射 用於調試
.strtab:字符串表存儲symtab、debug符號表中符號的名字
查看ELF文件內容、各段大小的命令:
1 readelf -a main
2 size main
gcc命令基本選項:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
庫的生成與使用:
(1)靜態庫
ar rcs fun.a fun1.o fun2.o
選項:r:把列表中的目標文件加入到靜態庫
c:若指定的靜態庫不存在則創建該文件
s:更新靜態文件的索引,使之包含新加入的目標文件的內容
鏈接時:
gcc main.c -lfun.a -o main
gcc -L. main.c -o main
-L緊跟靜態庫路徑
(2)動態庫
gcc -shared -fPIC -o lib.so lib,c
選項的含義:
-shared:生成動態庫
-fPIC:生成位置無關代碼
鏈接時:
gcc main.c ./lib.so -o main
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
可執行文件在運行時:
除了代碼段、數據段、BSS段,還有堆區和棧區
堆區:用於動態分配內存,用 malloc、free申請和釋放
從低地址向高地址增長
鏈式存儲
效率比棧低
棧區:由操作系統自動分配和釋放,存儲函數的參數值、局部變量的值等
從高地址向低地址增長
連續內存
最大容量固定