轉載自:https://www.cnblogs.com/wuyouxiaocai/p/5701088.html#commentform
說實話,很多人做了很久的C/C++,也用了很多IDE,但是對於可執行程序的底層生成一片茫然,這無疑是一種悲哀,可以想象到大公司面試正好被問到這樣的問題,有多悲催不言而喻,這里正由於換工作的緣故,所以打算系統的把之前用到的C/C++補一補。這里權且當做拋磚引玉,大神飄過。
【總述】
從一個源文件(.c)到可執行程序到底經歷了哪幾步,我想大多數的人都知道,到時到底每一步都做了什么,我估計也沒多少人能夠說得清清楚楚,明明白白。
其實總的流程是這樣的。
【第一步】編輯hello.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 int main() 4 { 5 printf("hello world!\n"); 6 return 0; 7 }
【第二步】預處理
預處理過程實質上是處理“#”,將#include包含的頭文件直接拷貝到hell.c當中;將#define定義的宏進行替換,同時將代碼中沒用的注釋部分刪除等
具體做的事兒如下:
(1)將所有的#define刪除,並且展開所有的宏定義。說白了就是字符替換
(2)處理所有的條件編譯指令,#ifdef #ifndef #endif等,就是帶#的那些
(3)處理#include,將#include指向的文件插入到該行處
(4)刪除所有注釋
(5)添加行號和文件標示,這樣的在調試和編譯出錯的時候才知道是是哪個文件的哪一行
(6)保留#pragma編譯器指令,因為編譯器需要使用它們。
gcc -E hello.c -o a.c可以生成預處理后的文件。通過查看文件內容和文件大小可以得知a.c講stdio.h和stdlib.h包含了進來。
【第三步】編譯
編譯的過程實質上是把高級語言翻譯成機器語言的過程,即對a.c做了這些事兒
(1)詞法分析,
(2)語法分析
(3)語義分析
(4)優化后生成相應的匯編代碼
從 高級語言->匯編語言->機器語言(二進制)
gcc -S hello.c -o a.s可以生成匯編代碼
匯編代碼如下。
1 .file "hello.c" 2 .section .rodata 3 .LC0: 4 .string "hello world!" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 .LFB0: 10 .cfi_startproc 11 pushl %ebp 12 .cfi_def_cfa_offset 8 13 .cfi_offset 5, -8 14 movl %esp, %ebp 15 .cfi_def_cfa_register 5 16 andl $-16, %esp 17 subl $16, %esp 18 movl $.LC0, (%esp) 19 call puts 20 movl $0, %eax 21 leave 22 .cfi_restore 5 23 .cfi_def_cfa 4, 4 24 ret 25 .cfi_endproc 26 .LFE0: 27 .size main, .-main 28 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" 29 .section .note.GNU-stack,"",@progbits
gcc -c hello.c -o a.o將源文件翻譯成二進制文件。類Uinx系統編譯的結果生生成.o文件,Windows系統是生成.obj文件。
編譯的過程就是把hello.c翻譯成二進制文件
【第四步】鏈接
就像剛才的hello.c它使用到了C標准庫的東西“printf”,但是編譯過程只是把源文件翻譯成二進制而已,這個二進制還不能直接執行,這個時候就需要做一個動作,
將翻譯成的二進制與需要用到庫綁定在一塊。打個比方編譯的過程就向你對你老婆說,我要吃雪糕。你只是給你老婆發出了你要吃雪糕的訴求而已,但是雪糕還沒有到。
綁定就是說你要吃的雪糕你的老婆已經給你買了,你可以happy。
gcc hello.c -o a可以生成可執行程序。即gcc不帶任何參數。ldd就可以看到你的可執行程序依賴的庫。
可以看到a.o的大小是1.1k,畢竟他只是把源文件翻譯成二進制文件。a卻有7k,應該是他多了很多“繩子”吧。在運行的時候這些“繩子”就將對應的庫函數“牽過來”。很形象的比喻是不是?哈哈。libc.so.6 中就對咱們用的printf進行了定義。
這就是編寫的整個流程,(⊙o⊙)。謝謝各位看官。不足的地方請不吝賜教。