第四篇 編譯

1. 編譯和連接

在這裏插入圖片描述## 1.1 預處理
命令:g++ -E helloworld.cpp -o helloworld.i

  • 預處理過程主要處理那些源代碼文件以“#”開始的預編譯指令,主要規則如下:
    (1)將所有的#define刪除,並且展開所有的宏定義
    (2)處理所有條件預編譯指令,比如#if、#ifdef、#elif、#else、#endif
    (3)處理#include預編譯指令,將被包含的文件插入到該預編譯指令的位置
    (4)過濾所有的註釋“//”和"/**/"
    (5)添加行號和文件名標識
    (6)保留所有的#pragma預編譯指令,因爲編譯器需要使用它們

1.2 編譯

編譯過程就是把預處理完的文件進行一系列的詞法分析、語法分析、語義分析、以及優化後產生相應的彙編代碼文件。
命令:g++ -S helloworld.i -o helloworld.s
這裏用的大寫的S,而不是小寫s,因爲大小寫s的編譯選項表示含義都是不一樣, -s 表示直接生成與運用strip同樣效果的可執行文件(刪除了所有符號信息)

  • 編譯過程
    在這裏插入圖片描述

1.3 鏈接

鏈接的主要內容就是把各個模塊之間相互引用的部分都處理好,使得各個模塊之間能夠正確的銜接;
鏈接過程主要包括了地址和空間分配、符號決議和重定位等步驟;
每個目標文件除了擁有自己的數據和二進制代碼外,還提供了3個表:未解決符號表、導出符號表、地址重定向表,具體描述如下:
(1)未解決符號表提供了所有在該編譯單元裏引用但是定義並不在本編譯單元的符號及其出現的地址;
(2)導出符號表提供了本編譯單元具有定義,並且願意提供給其他單元使用的符號及其地址;
(3)地址重定向表提供了本編譯單元所有對自身地址的引用的記錄。

  • ar命令介紹
    r----在庫中插入模塊
    c----創建一個庫
    tv----可以顯示庫文件中有哪些目標文件,顯示文件名、時間、大小等詳細信息
  • 編譯動態庫命令詳解
    (1)-fPIC:表示編譯爲位置獨立的代碼,不適用此選項的話編譯後的代碼是位置相關的,所以動態載入時是通過代碼複製的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的;
    (2)-Lpath:表示在path目錄中搜索庫文件
    (3)-Ipath:(大寫I)表示在path目錄中搜索頭文件
    (4)-ltest:(小寫L)編譯器查找動態鏈接庫時有隱含的命名規則,即在給出的名字前面加lib,後面加.so來確定庫的名稱
  • 動態庫的搜索路徑搜索的先後順序如下:
    (1)編譯目標代碼時指定的動態庫搜索路徑("-Wl,-rpath,“指定,當指定多個動態庫搜索路徑時,路徑之間用冒號”:"分隔);
    (2)環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
    (3)配置文件/etc/ld.so.conf中指定的動態庫搜索路徑(即只需在該文件中追加一行庫所在的完整路徑,然後ldconfig生效);
    (4)默認動態庫搜索路徑/lib
    (5)默認動態庫搜索路徑/usr/lib
  • 動態庫和靜態庫重名時,編譯器會先到path目錄下搜索libxxx.so文件,如果沒有找到,則繼續搜索libxxx.a文件

1.4 g++與gcc的區別

  • 誤區:
    (1)gcc只能編譯c代碼,g++只能編譯c++代碼。
    ---->1.後綴爲.c的,gcc把它當作是c程序,而g++當作c++程序;後綴爲.cpp的,兩者都會認爲是c++程序;
    ---->2.編譯階段,g++會調用gcc,對於c++代碼,二者是等價的;但是因爲gcc命令不能自動和c++程序使用的庫鏈接,所以通常用g++來完成鏈接,爲了統一起見,乾脆編譯/鏈接都統一使用g++,這就給人一種錯覺,好像cpp程序只能用g++似的;
    (2)gcc不會定義__cplusplus,而g++會;
    實際上,這個宏只是標誌着編譯器將會把代碼按c還是c++語法來解釋,如上所述,如果後綴爲.c,並且採用gcc編譯器,則改宏就是未定義的,否則就是已定義的
    (3)編譯只能用gcc,鏈接只能用g++;
    正解:編譯可以用gcc/g++,而鏈接可以用g++或者gcc-lstdc++
    (4)extern "C"與gcc/g++有關係

1.5 makefile的撰寫

  • g++選項
    (1)-c—>g++只把給它的文件編譯成目標文件
    (2)-Wall—>輸出所有的警告信息
    (3)-O—>編譯時進行優化
    (4)-g—>編譯debug版本
  • 在makefile規則中,通配符會被自動展開。但是在變量的定義和函數的引用時,通配符將失效。這種情況下如果需要通配符有效,就需要使用函數wildcard。
  • $(wildcard PATTERN…)—>在makefile中,它被展開爲已經存在的、使用空格分開的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函數會忽略模式字符並返回空。
    eg: SOURCES = $(wildcard *.c *.cpp)
  • patsubst函數—>用於匹配替換,有3個參數。第一個是一個需要匹配的式樣,第二個表示用什麼來替換他,第三個是一個需要被處理的由空格分割的列表
    eg:OBJS = $(patsubst %c, %o, $(dir))
    OBJS = $(patsubst %c, %o, $(patsubst %.cpp, %o, $(SOURCES)))
    第二句命令的例子表示吧所有的.c、.cpp文件編譯成.o文件
  • notdir:去除所有的目錄信息,SRC裏的文件名列表將只有文件名
    SRC = $(notdir wildcard)
  • fillter-out:把text中的pattern信息去除
    $(filter-out pattern…,text)
  • 介紹幾個內部變量
    (1)$@—>擴展成當前規則的目的文件名
    (2)$<—>擴展成依靠列表中的第一個依靠文件
    (3)$^—>擴展成整個依靠列表(除掉了裏面所有重複的文件名)

1.6 目標文件

  • 目標文件的分類:
    (1)可重定位的目標文件(.o)
    (2)可執行的目標文件
    (3)可被共享的目標文件
  • 分析文件常用工具
    (1)file—>查看文件是屬於那種ELF文件
    (2)readelf
    -h選項–>讀出整個elf文件頭的內容
    -S選項–>查看可重定位對象文件的section表內容
    -x選項–>打印出不同section的內容(eg:readelf -x num file以16進制方式顯示指定段內內容。num表示指定段表中段的索引或字符串指定文件中的段名)
    -s選項–>顯示符號表段中的項
    -l選項–>顯示文件程序頭的信息
    (3)objdump
    eg:objdump -d -j .text add.o
    -d:表示要對由-j選項指定的section內容進行反彙編,也就是由機器碼出發,推到出相應的彙編代碼
    -t:查看符號表中的內容
    (4)hexdump
    eg:hexdump -s 0x508 -n 32 -c add.o(用hexdump直接dumping出.strtab section開頭的32byte數據,0x508是readelf -S add.o後看到的.strtab section的offset值)
    (5)獲取二進制文件裏符號的工具----nm
    nm是用來查看指定程序中的符號表相關內容的工具
    nm命令對程序的幫助,主要有以下幾個方面
    –>1. 判斷指定程序中有沒有後定義指定的符號
    –>2.解決程序編譯時undefined reference的錯誤以及mutiple definition的錯誤
    –>3.查看某個符號的地址,以及在進程空間的大概位置
    (5)減少目標文件大小的工具----strip
    strip能清除執行文件中不必要的標示符及調試信息,可減小文件大小而不影響正常使用。
    –>-l(小寫L):從對象文件中取出行號信息
    –>-r:除了外部符號和靜態符號條目,將全部符號表信息除去,不除去重定位信息,同時除去調試段和typchk段,這個選項產生一個對象文件,該對象文件扔可以用作輸入到鏈接編譯器中
    –>-t:除去大多數符號表信息,但並不除去函數符號或行號信息
    –>-V:打印strip命令的版本號
    –>-x:除去符號表信息,但並不除去靜態或外部符號信息,同時除去重定位信息,因此將不可能鏈接到該文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章