C/C++源程序到可執行程序的過程


 

  源程序.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申請和釋放

              從低地址向高地址增長

              鏈式存儲

                   效率比棧低

  棧區:由操作系統自動分配和釋放,存儲函數的參數值、局部變量的值等

                  從高地址向低地址增長

        連續內存

                  最大容量固定

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM