背景
有時我們的程序會定義一些暫時使用不上的功能和函數,雖然我們不使用這些功能和函數,但它們往往會浪費我們的ROM和RAM的空間。這在使用靜態庫時,體現的更為嚴重。有時,我們只使用了靜態庫僅有的幾個功能,但是系統默認會自動把整個靜態庫全部鏈接到可執行程序中,造成可執行程序的大小大大增加。
參數詳解
為了解決前面分析的問題,我們引入了標題中的幾個參數。GCC鏈接操作是以section作為最小的處理單元,只要一個section中的某個符號被引用,該section就會被加入到可執行程序中去。因此,GCC在編譯時可以使用 -ffunction-sections 和 -fdata-sections 將每個函數或符號創建為一個sections,其中每個sections名與function或data名保持一致。而在鏈接階段, -Wl,–gc-sections 指示鏈接器去掉不用的section(其中-wl, 表示后面的參數 -gc-sections 傳遞給鏈接器),這樣就能減少最終的可執行程序的大小了。
我們常常使用下面的配置啟用這個功能:
CFLAGS += -ffunction-sections -fdata-sections LDFLAGS += -Wl,--gc-sections
例子
main.c 文件如下:
#include <stdio.h> int fun_0(void) { printf("%s: %d\n", __FUNCTION__, __LINE__); return 0; } int fun_1(void) { printf("%s: %d\n", __FUNCTION__, __LINE__); return 0; } int fun_2(void) { printf("%s: %d\n", __FUNCTION__, __LINE__); return 0; } int fun_3(void) { printf("%s: %d\n", __FUNCTION__, __LINE__); return 0; } void main(void) { fun_0(); fun_3(); }
Makefile文件如下:
main_sections: gcc -ffunction-sections -fdata-sections -c main.c gcc -Wl,--gc-sections -o $@ main.o main_normal: gcc -c main.c gcc -o $@ main.o clean: rm -rf *.o main_sections main_normal
驗證
運行
$ make main_sections gcc -ffunction-sections -fdata-sections -c main.c gcc -Wl,--gc-sections -o main_sections main.o $ make main_normal gcc -c main.c gcc -o main_normal main.o
比較大小
$ ll main_* -rwxrwxr-x 1 8896 2月 16 00:42 main_normal* -rwxrwxr-x 1 8504 2月 16 00:42 main_sections*
可以看見使用該功能的二進制文件要小於不使用該功能的二進制文件
分析sections
$ make clean rm -rf *.o main_sections main_normal $ make main_sections gcc -ffunction-sections -fdata-sections -c main.c gcc -Wl,--gc-sections -o main_sections main.o $ readelf -t main.o ... [ 5] .text.fun_0 PROGBITS PROGBITS 0000000000000000 0000000000000048 0 0000000000000024 0000000000000000 0 1 [0000000000000006]: ALLOC, EXEC [ 6] .rela.text.fun_0 RELA RELA 0000000000000000 0000000000000508 24 0000000000000048 0000000000000018 5 8 [0000000000000040]: INFO LINK [ 7] .text.fun_1 PROGBITS PROGBITS 0000000000000000 000000000000006c 0 0000000000000024 0000000000000000 0 1 [0000000000000006]: ALLOC, EXEC [ 8] .rela.text.fun_1 RELA RELA 0000000000000000 0000000000000550 24 0000000000000048 0000000000000018 7 8 [0000000000000040]: INFO LINK [ 9] .text.fun_2 PROGBITS PROGBITS 0000000000000000 0000000000000090 0 0000000000000024 0000000000000000 0 1 [0000000000000006]: ALLOC, EXEC [10] .rela.text.fun_2 RELA RELA 0000000000000000 0000000000000598 24 0000000000000048 0000000000000018 9 8 [0000000000000040]: INFO LINK [11] .text.fun_3 PROGBITS PROGBITS 0000000000000000 00000000000000b4 0 0000000000000024 0000000000000000 0 1 [0000000000000006]: ALLOC, EXEC [12] .rela.text.fun_3 RELA RELA 0000000000000000 00000000000005e0 24 0000000000000048 0000000000000018 11 8 [0000000000000040]: INFO LINK
從object文件中可以發現,fun_0 ~ fun_3 每個函數都是一個獨立的section.
而如果使用 make main_normal 生成的object文件,則共享一個默認的sections(.text)。
分析elf文件
$ readelf -a main_normal | grep fun_ 52: 0000000000400526 36 FUNC GLOBAL DEFAULT 14 fun_0 55: 000000000040056e 36 FUNC GLOBAL DEFAULT 14 fun_2 65: 0000000000400592 36 FUNC GLOBAL DEFAULT 14 fun_3 66: 000000000040054a 36 FUNC GLOBAL DEFAULT 14 fun_1 $ readelf -a main_sections | grep fun_ 49: 0000000000400526 36 FUNC GLOBAL DEFAULT 14 fun_0 57: 000000000040054a 36 FUNC GLOBAL DEFAULT 14 fun_3
可以看見,在最終的目標文件中,未使用的函數並未被鏈接進最終的目標文件。