gcc/g++命令使用及編譯原理二

一、概述

對於編譯原理,如果要系統的學習的話,知識點多的完全可以寫成一本書,推薦大家可以去閱讀一下《編譯原理》這本書,可以全面深入的學習編譯原理,很經典。接下來的文章我僅僅對GCC/G++編譯原理寫一些記錄。

其中gcc命令是對c語言文件的編譯,g++命令是對c++文件的編譯,其命令的原理其實是一樣的。

二、編譯流程

gcc命令編譯一般分爲四個步驟及調用的命令:

  • 預處理(preprocessing)  :gcc -E 
  • 編譯(compilation) :gcc -S
  • 彙編(assembly) :gcc -c對應調用的是as命令
  • 連接(linking)  :gcc對應調用的是ld 命令

先上一個醜陋的概念圖

源文件,以下是hello.c代碼

#include<stdio.h>

int main(){
	printf("hello world!");
	return 0;
}

預處理階段

預處理階段主要處理#include和#define,它把#include包含進來的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定義的宏用實際的字符串代替,刪除註釋。

 

執行命令,會生成hello.i文件

$ gcc -E hello.c -o hello.i

# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 29 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/_ansi.h" 1 3 4
# 10 "/usr/include/_ansi.h" 3 4
# 1 "/usr/include/newlib.h" 1 3 4
# 14 "/usr/include/newlib.h" 3 4
# 1 "/usr/include/_newlib_version.h" 1 3 4
# 15 "/usr/include/newlib.h" 2 3 4
# 11 "/usr/include/_ansi.h" 2 3 4
# 1 "/usr/include/sys/config.h" 1 3 4



# 1 "/usr/include/machine/ieeefp.h" 1 3 4
# 5 "/usr/include/sys/config.h" 2 3 4
# 1 "/usr/include/sys/features.h" 1 3 4
# 6 "/usr/include/sys/config.h" 2 3 4
# 234 "/usr/include/sys/config.h" 3 4
# 1 "/usr/include/cygwin/config.h" 1 3 4
# 235 "/usr/include/sys/config.h" 2 3 4
# 12 "/usr/include/_ansi.h" 2 3 4
# 30 "/usr/include/stdio.h" 2 3 4

.....此處忽略一千行

# 797 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2


# 3 "hello.c"
int main(){
 printf("hello world!");
 return 0;
}

編譯階段

編譯階段首先檢查代碼的規範性,語法錯誤,最終把代碼翻譯成彙編語言

執行命令:

$ gcc -S hello.i -o hello.s

以下是生成的hello.s彙編代碼,有興趣的同學可以研究一下,對於反彙編,這一份代碼是必需看懂的^-^。

	.file	"hello.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
.LC0:
	.ascii "hello world!\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	leaq	.LC0(%rip), %rcx
	call	printf
	movl	$0, %eax
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (GNU) 7.3.0"
	.def	printf;	.scl	2;	.type	32;	.endef

彙編階段

彙編階段把hello.s文件翻譯成二進制機器指令,把hello.s文件轉換成hello.o文件,執行命令:

 

$ gcc -c hello.s -o hello.o

生成的文件是二進制格式,普通人是看不懂了,這是一份讓機器理解的代碼

當然,我們也可以通過ida,od等工具反彙編得到彙編代碼

鏈接階段

很多人都會認爲到了彙編階段產出的.o文件就可以使用了,當然這個想法是錯誤的,還需要最後一個階段,就是鏈接階段(Linking),可以通俗的理解爲關聯繫統的api,我們的程序調用stdio.h中的printf函數,而這個標準的打印函數是由系統提供的,而鏈接階段就是告訴我們應用調用prinft的地方,該去哪裏調用這個函數的實現地方,有可能是系統的標準庫函數庫,也有可能是外面提供的函數庫,反正結果就是讓我們的應用知道在哪裏去找到這個函數。

執行命令,最終生成hello可執行的文件:

$ gcc hello.o -o hello

 

函數庫一般分爲靜態庫和動態庫兩種

  • 靜態庫是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了,其後綴名一般爲”.a”,如前面的應用,會把printf函數相關的依賴函數都會打包進來。
  • 動態庫與之相反,在編譯鏈接時並沒有把庫文件的代碼加入到可執行文件中,而是在程序執行時由運行時鏈接文件加載庫,這樣可以節省系統的開銷。動態庫一般後綴名爲”.so”,gcc在編譯時默認使用動態庫,如果系統把printf函數不小心刪除了,前面的應用在運行的時候就會報找不到libc.so.6庫的錯誤提示。

文章所有的例子代碼工程

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章