對於c語言,老是想搞搞,之前編寫c語言都是在IDE中一個build一個run就看結果,雖然對預處理彙編之類的中間過程也有過了解,但時間一久就忘了,so,今天就寫下個筆記,來看看由一個hello.c源文件到產生一個可執行文件的整個過程。本例子在linux平臺下運行:linux
寫個hello.c源文件吧.編程
#include<stdio.h>
int main()vim
{ide
printf("hello,world\n");ui
return 0;spa
}翻譯
ok,hello.c源文件寫完。string
一個.c源文件到產生目標文件要經歷4個步驟,分別是:預處理階段 -> 編譯階段 -> 彙編階段 -> 連接階段。it
(1)預處理階段(加入相應庫,宏替換等)io
gcc hello.c -E > e.txt ( >號是重定向,把預處理的結果輸入到e.txt文件中 )
預處理階段是用預處理器處理的,它只是簡單的把stdio.h庫簡單的加入到文件中,查看e.txt文件以下:
...前面省略不少
extern char *ctermid (char *__s) __attribute__ ((__nothrow__));
# 908 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));
# 938 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
int main()
{
printf("hello,world\n");
return 0;
}
能夠看到,在這裏預處理階段只是把相應的庫文件添加到程序中。
(2)編譯階段(生成彙編程序)
gcc hello.c -S 這裏hello.c通過預處理和編譯,生成hello.s的彙編文件,它是一個文本文件,以下:
.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, %edi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-23)"
.section .note.GNU-stack,"",@progbits
(3)彙編階段(把彙編語言程序彙編成機器語言指令,並使用可重定位目標程序的格式來打包指令)
gcc hello.c -c 運行會把hello.c文件經過預處理,編譯,彙編三個階段,生成.o文件,hello.o文件是一個二進制文件不是文本文件,在這個階段彙編器會把第二階段生成的hello.s文件翻譯成機器語言指令,並把這些指令打包成一種叫作可重定位目標程序的格式,而後將結果存放在hello.o文件中,使用vim -b hello.o查看能夠發現都是亂碼,其中一行比較醒目以下:
^@UH<89><e5><bf>^@^@^@^@<e8>^@^@^@^@<b8>^@^@^@^@<c9><c3>^@^@^@hello,world^@^@GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-23)^@^@^@^@^@^@^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A
看到hello,world字符,就是咱們要打印的字符。
(4)連接階段(合併各個打包好的機器語言指令,生成可執行二進制文件)
gcc hello.o -o hello
在這個階段,連接器把printf.o文件和hello.o文件合併在一塊兒,生成一個可執行文件hello,經過運行:./hello
結果輸入以下:
hello,world
比較有意思的事情是,當咱們使用vim -b hello查看可執行目標文件時,發現頗有意思的一行以下:
3>^@^@^A^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@hello,world^@^A^[^C; ^@^@^@^C^@^@^@<e0><fe><ff><ff><^@^@^@<fc><fe><ff><ff>\^@^@^@^L<ff><ff><ff>t^@^@^@^T^@^@^@^@^@^@^@^AzR^@^A
看到沒,那個hello,world簡直就是黑夜中的燈火,太亮眼了,我想,若是我直接改這幾個字符再運行,會不會輸出另外的結果呢?
因而把 hello,world 改爲 hello,c ,而後保存,在期待中運行: ./hello
輸出:
段錯誤 (core dumped)
這是什麼狀況???
查查發現,這應該和彙編階段產生的可重定位目標程序的格式有關,連接時已經使用該格式來描述程序。關於可重定位的內容比較複雜,這裏就不說了,在這個可執行hello程序中,若是想經過改變hello的內容來讓結果改變,要作的就是一個蘿蔔一個坑,使用其餘文本代替hello,world文件便可。
在這裏使用 ilovehaha,c 來代替 hello,world, 要注意代替的字符必須和被代替的字符長度相同,好了,更改後在期待中輸入:./hello
輸出:
ilovehaha,c
驗證結論,這就是一個hello.c源文件通過處理後生成hello可執行文件的有趣過程,over