C程序編程四步走(c編程步驟)

任何一個 C 程序代碼到生成一個可執(zhí)行文件都需要四步,分別是預(yù)處理 Pre-processing ,編譯 Compiling ,匯編 Assembling 和鏈接 Link ,這里借助 Gcc 工具來探究這四步分別做了什么事,起到什么樣的作用。本文使用的測試代碼是經(jīng)典入門程序 "Hello World!"。

測試環(huán)境

為探究預(yù)處理,編譯,匯編和鏈接的功能,我們在 Ubuntu 系統(tǒng)中使用 Gcc 編譯器( version=4.8.4 ),簡單的也是最經(jīng)典的入門程序 "Hello World!" 作為測試代碼。源文件 hello.c[1] 代碼如下:

// filename: hello.c# include <stdio.h>int main(void){ printf("Hello World!"); return 0;}

正常情況我們都會執(zhí)行命令 gcc hello.c -o hello.out 來生成二進(jìn)制可執(zhí)行程序 hello.out。

預(yù)處理[2]

C 預(yù)處理器是用在編譯器處理程序之前,它預(yù)掃描源代碼完成包含頭文件,宏擴(kuò)展條件編譯,行控制等功能。對于測試代碼中,預(yù)處理器只對頭文件進(jìn)行了處理。獲取預(yù)處理器輸出的結(jié)果使用該命令 gcc -E hello.c -o hello.i。由于 hello.i[3] 文件內(nèi)容比較多,這里截取部分進(jìn)行說明。

// filename: hello.i# 1 "hello.c"# 1 "<built-in>"# 1 "<command-line>"# 1 "/usr/include/stdc-predef.h" 1 3 4# 1 "<command-line>" 2# 1 "hello.c"...# 1 "/usr/lib/gcc/x86_64-linux-gnu/4.8/include/stddef.h" 1 3 4# 212 "/usr/lib/gcc/x86_64-linux-gnu/4.8/include/stddef.h" 3 4typedef long unsigned int size_t;...# 5 "hello.c" 2int main(){ printf("Hello World!"); return 0;}

Tips:
hello.i 中有很多這樣的格式
# line filename flags,它表示下面行是由文件 filename 的第 line 行生成的。其中 flags 有 1,2,3,4 四種取值

? 1 代表新文件的開始

? 2 代表返回一個文件

? 3 代表下面的文本來自系統(tǒng)頭文件,所以某些警告可以過濾掉

? 4 代表下面的文本應(yīng)該包含在extern C塊中 按照提示 stddef.h 文件中第 212 行有 size_t 的宏定義。

編譯[4]

編譯的過程是將某種編程語言寫的源代碼(這里特指 C 語言)轉(zhuǎn)換成另一種編程語言(這里特指匯編語言)。前面我們將 hello.c 預(yù)處理成了 hello.i 文件,現(xiàn)在就要將 hello.i 文件編譯成匯編文件 hello.s 。獲取編譯器輸出的結(jié)果使用命令 gcc -S hello.i -o hello.s 。匯編結(jié)果見 hello.s[5] 。

.file "hello.c" .section .rodata .LC0: .string "Hello World!" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, ?i movl $0, ?x call printf movl $0, ?x popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4" .section .note.GNU-stack,"",@progbits

匯編[6]

匯編的過程是將匯編語言編寫的源碼轉(zhuǎn)換成可執(zhí)行的機(jī)器代碼,通常目標(biāo)文件中包含至少兩個段:代碼段和數(shù)據(jù)段。其中代碼段包含程序的指令,一般可讀和可執(zhí)行,不可寫;數(shù)據(jù)段用來存放程序中所用到的各種全局變量或靜態(tài)數(shù)據(jù),一般可讀,可寫,可執(zhí)行。獲取匯編器輸出的結(jié)果使用該命令 gcc -o hello.o -c hello.c ,由于 hello.o 是二進(jìn)制文件,是無法閱讀的。這里我們通過命令 objdump 來對二進(jìn)制文件進(jìn)行反匯編,查看里面內(nèi)容。

// objdump -d hello.o 查看hello.o中代碼段信息hello.o:文件格式 elf64-x86-64Disassembly of section .text:0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: bf 00 00 00 00 mov $0x0,?i 9: b8 00 00 00 00 mov $0x0,?x e: e8 00 00 00 00 callq 13 <main 0x13> 13: b8 00 00 00 00 mov $0x0,?x 18: 5d pop %rbp 19: c3 retq

hello.o中各段信息如下:

// objdump -h hello.o 顯示hello.o中各個段的頭部信息hello.o:文件格式 elf64-x86-64節(jié):Idx Name Size VMA LMA File off Algn 0 .text 0000001a 0000000000000000 0000000000000000 00000040 2**0 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 0000000000000000 0000000000000000 0000005a 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 0000000000000000 0000000000000000 0000005a 2**0 ALLOC 3 .rodata 0000000d 0000000000000000 0000000000000000 0000005a 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .comment 0000002c 0000000000000000 0000000000000000 00000067 2**0 CONTENTS, READONLY 5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000093 2**0 CONTENTS, READONLY 6 .eh_frame 00000038 0000000000000000 0000000000000000 00000098 2**3 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

鏈接[7]

鏈接的過程是將一個或多個由編譯器或匯編器生成的目標(biāo)文件鏈接庫(靜態(tài)庫或動態(tài)庫)形成可執(zhí)行文件。其中靜態(tài)庫會和匯編生成的目標(biāo)文件一起鏈接打包到可執(zhí)行文件中(靜態(tài)鏈接),它對函數(shù)庫的鏈接是放在編譯時期完成的。而動態(tài)庫在程序編譯時不會被鏈接到可執(zhí)行文件中,而是在程序運(yùn)行時才會被載入(動態(tài)鏈接)。不同的應(yīng)用程序如果調(diào)用相同的庫,那么在內(nèi)存中只需要一份該共享庫實例。獲取鏈接器鏈接后的可執(zhí)行文件使用命令 gcc hello.o -o hello 。如果想看該可執(zhí)行文件依賴的庫,可以使用命令 ldd hello 。

# ldd hello 顯示hello依賴的庫 linux-vdso.so.1 => (0x00007ffc85980000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f06c7a53000) /lib64/ld-linux-x86-64.so.2 (0x000055ad7be9e000)

參考文獻(xiàn)

1.預(yù)處理[8]

2.預(yù)處理-行號標(biāo)記[9]

3.編譯器[10]

4.匯編[11]

5.鏈接器[12]

References

[1] hello.c: https://blog.haojunyu.com/atts/hello.c
[2] 預(yù)處理:
https://zh.wikipedia.org/wiki/C預(yù)處理器
[3] hello.i:
https://blog.haojunyu.com/atts/hello.i
[4] 編譯:
https://zh.wikipedia.org/wiki/編譯器
[5] hello.s:
https://blog.haojunyu.com/atts/hello.s
[6] 匯編:
https://zh.wikipedia.org/wiki/匯編語言
[7] 鏈接:
https://zh.wikipedia.org/wiki/鏈接器
[8] 預(yù)處理:
https://zh.wikipedia.org/wiki/C預(yù)處理器
[9] 預(yù)處理-行號標(biāo)記:
https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
[10] 編譯器:
https://zh.wikipedia.org/wiki/編譯器
[11] 匯編:
https://zh.wikipedia.org/wiki/匯編語言
[12] 鏈接器:
https://zh.wikipedia.org/wiki/鏈接器

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號
公眾號
在線咨詢
分享本頁
返回頂部