--- title: 'GCC 編譯、Makefile' disqus: kyleAlien --- GCC 編譯、Makefile === ## OverView of Content 這裡會使用 gcc 編譯 .c 檔案,並寫一個簡單的 Makefile [TOC] ## 安裝 gcc 1. 當前環境使用 Ubutun 20 版本 ```shell= # install gcc from apt sudo apt install gcc ``` > ![](https://i.imgur.com/Diq2NXp.png) 2. 寫一個簡單的 test.c 檔案,動態申請記憶體空間 ```c= // test.c #include <stdio.h> #include <stdlib.h> #include <string.h> #define PAGE_SIZE 4096 #define MAX_SIZE 100*PAGE_SIZE int main() { char *buf = (char*)malloc(MAX_SIZE); memset(buf, 0, MAX_SIZE); printf("buffer address =0x%p\n", buf); free(buf); return 0; } ``` ## 編譯 編譯分為 4 個步驟 1. 預編譯 2. 編譯 3. 彙編 4. 鏈結 ### 預編譯 * 透過 gcc 使用以下指令,預編譯會先編譯檔案中定義好的 #define ```shell= # -E 是預編譯 # -o 是輸出檔案 aarch64-linux-gnu-gcc -E test.c -o test.i ``` * 預編譯結果 ```c= // test.i extern void *malloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__mallo c__)) __attribute__ ((__alloc_size__ (1))) ; ... 省略部分 int main() { char *buf = (char*)malloc(100*4096); // 替換為 定義好的數字 memset(buf, 0, 100*4096); // 替換為 定義好的數字 printf("buffer address =0x%p\n", buf); free(buf); return 0; } ``` ### 編譯 * 在這個步驟中會去 **檢查語法是否錯誤,並 ++產生==彙編碼==++**,透過 gcc 使用以下指令,預編譯會先編譯檔案中定義好的 #define ```shell= # -S 是編譯 # -o 是輸出檔案 aarch64-linux-gnu-gcc -S test.i -o test.s ``` * 編譯結果 (結果是彙編碼) ```cmake= .arch armv8-a .file "test.c" .text .section .rodata .align 3 .LC0: .string "buffer address =0x%p\n" .text .align 2 .global main .type main, %function main: .LFB6: .cfi_startproc stp x29, x30, [sp, -32]! .cfi_def_cfa_offset 32 .cfi_offset 29, -32 .cfi_offset 30, -24 mov x29, sp mov x0, 16384 movk x0, 0x6, lsl 16 bl malloc str x0, [sp, 24] mov x2, 16384 movk x2, 0x6, lsl 16 mov w1, 0 ldr x0, [sp, 24] bl memset ldr x1, [sp, 24] adrp x0, .LC0 add x0, x0, :lo12:.LC0 bl printf ldr x0, [sp, 24] bl free mov w0, 0 ldp x29, x30, [sp], 32 .cfi_restore 30 .cfi_restore 29 .cfi_def_cfa_offset 0 ret .cfi_endproc .LFE6: .size main, .-main .ident "GCC: (Ubuntu 11.2.0-7ubuntu2) 11.2.0" .section .note.GNU-stack,"",@progbits ``` :::danger * 若語法錯誤會在 gcc`編譯` 查出 > ![](https://i.imgur.com/irqXAKU.png) ::: ### 彙編 * 將上個步驟的彙編文件轉為 2 進制檔案 ```shell= # -c 是彙編 # -o 是輸出檔案 aarch64-linux-gnu-gcc -c test.s -o test.o ``` * 編譯結果 (結果是 2 進制) > ![](https://i.imgur.com/SJ0EcoG.png) ### 鏈結 * 鏈結主要是在鏈結其他 Library,像是我們使用的 `printf`、`memset`、`free` 都是透過 include 拉進來,而在鏈結的階段,它就會連接這些函式庫 ```shell= # -o 是輸出檔案 aarch64-linux-gnu-gcc test.o -o test ``` * Linux 内核的 Library 分為 **兩大類**,^1^ **動態庫 (.so 文件)**,^2^ **靜態庫 (.a 文件)** GCC 在連結時預設先使用 動態庫,若動態庫不存在才會使用靜態庫 ```shell= # -o 是輸出檔案 # --static 使用靜態庫 aarch64-linux-gnu-gcc test.o -o test --static ``` :::success * ARM64 GCC 交叉編譯靜態 Library 為 `libc.a`,動態 Libray 為 `libc.so` > ![](https://i.imgur.com/sZ4s3iU.png) ::: ## Makefile 透過 make 工具幫我們編譯出我們需要的檔案 ### 安裝 make 工具 * 當前環境使用 Ubutun 20 版本 ```shell= sudo apt install make ``` > ![](https://i.imgur.com/8tz4qej.png) ### Makefile 指令格式 ```shell= TARGET: Prerequisites Commands ``` 1. TARGET:目標生成的文件 2. Prerequisites:目標所依賴的所有文件 (可多個) 3. Commands:當 Prerequisites 有任何一個比 TARGET 新時都會觸發到 Command 命令的執行,具體命令是依照使用者需求所設置 (eg. 調用 shell, gcc, java 等等...) :::warning * Commands 前必須要有一個 Tab 空格 ::: ### 編寫 Makefile 腳本 1. 創建 makefile 檔案 ```shell= touch makefile ``` 2. 撰寫 Makefile 檔案 ```shell= ## makefile 腳本 ## 以下四個都是變數 cc = aarch64-linux-gnu-gcc prom = test obj = test.o CFLAGS = -static ## 條件滿足 obj 就執行以下指令 $(prom) : $(obj) $(cc) -o $(prom) $(obj) $(CFLAGS) ## 條件滿足 obj 就執行以下指令 %.o : %.c $(cc) -c $< -o $@ ## 清除指定檔案 clean: rm -rf $(obj) $(prom) ``` 3. 執行 make 指令 ```shell= ## 在該目錄下執行 make make ``` > ![](https://i.imgur.com/iGe5fWX.png) 4. 之後可以腳本內的 clean 清除產生的檔案 ```shell= ## 執行腳本中的 clean 命令 make clean ``` > ![](https://i.imgur.com/XNoVjpw.png) ## Appendix & FAQ :::info ::: ###### tags: `Linux 基礎`