聯(lián)系我們 - 廣告服務(wù) - 聯(lián)系電話:
您的當(dāng)前位置: > 關(guān)注 > > 正文

從源代碼到二進(jìn)制行程序 C語言的編譯過程是怎樣的?

來源:CSDN 時(shí)間:2023-01-12 10:33:08

前言

C語言程序從源代碼到二進(jìn)制行程序都經(jīng)歷了那些過程?本文以Linux下C語言的編譯過程為例,講解C語言程序的編譯過程。

編寫hello world C程序:


【資料圖】

// hello.c#includeint main(){    printf("hello world!\n");}

編譯過程只需:

$ gcc hello.c # 編譯$ ./a.out # 執(zhí)行hello world!

這個(gè)過程如此熟悉,以至于大家覺得編譯事件很簡單的事。事實(shí)真的如此嗎?我們來細(xì)看一下C語言的編譯過程到底是怎樣的。

上述gcc命令其實(shí)依次執(zhí)行了四步操作:1.預(yù)處理(Preprocessing), 2.編譯(Compilation), 3.匯編(Assemble), 4.鏈接(Linking)。

示例

為了下面步驟講解的方便,我們需要一個(gè)稍微復(fù)雜一點(diǎn)的例子。假設(shè)我們自己定義了一個(gè)頭文件mymath.h,實(shí)現(xiàn)一些自己的數(shù)學(xué)函數(shù),并把具體實(shí)現(xiàn)放在mymath.c當(dāng)中。然后寫一個(gè)test.c程序使用這些函數(shù)。程序目錄結(jié)構(gòu)如下:

├── test.c└── inc    ├── mymath.h    └── mymath.c

程序代碼如下:

// test.c#include#include "mymath.h"http:// 自定義頭文件int main(){    int a = 2;    int b = 3;    int sum = add(a, b);     printf("a=%d, b=%d, a+b=%d\n", a, b, sum);}

頭文件定義:

// mymath.h#ifndef MYMATH_H#define MYMATH_Hint add(int a, int b);int sum(int a, int b);#endif

頭文件實(shí)現(xiàn):

// mymath.cint add(int a, int b){    return a+b;}int sub(int a, int b){    return a-b;}

1.預(yù)處理(Preprocessing)

預(yù)處理用于將所有的#include頭文件以及宏定義替換成其真正的內(nèi)容,預(yù)處理之后得到的仍然是文本文件,但文件體積會大很多。gcc的預(yù)處理是預(yù)處理器cpp來完成的,你可以通過如下命令對test.c進(jìn)行預(yù)處理:

gcc -E -I./inc test.c -o test.i

或者直接調(diào)用cpp命令

$ cpp test.c -I./inc -o test.i

上述命令中-E是讓編譯器在預(yù)處理之后就退出,不進(jìn)行后續(xù)編譯過程;-I指定頭文件目錄,這里指定的是我們自定義的頭文件目錄;-o指定輸出文件名。

經(jīng)過預(yù)處理之后代碼體積會大很多:

X文件名文件大小代碼行數(shù)

預(yù)處理前test.c146B9

預(yù)處理后test.i17691B857

預(yù)處理之后的程序還是文本,可以用文本編輯器打開。

2.編譯(Compilation)

這里的編譯不是指程序從源文件到二進(jìn)制程序的全部過程,而是指將經(jīng)過預(yù)處理之后的程序轉(zhuǎn)換成特定匯編代碼(assembly code)的過程。編譯的指定如下:

$ gcc -S -I./inc test.c -o test.s

上述命令中-S讓編譯器在編譯之后停止,不進(jìn)行后續(xù)過程。編譯過程完成后,將生成程序的匯編代碼test.s,這也是文本文件,內(nèi)容如下:

// test.c匯編之后的結(jié)果test.s    .file   "test.c"    .section    .rodata.LC0:    .string "a=%d, b=%d, a+b=%d\n"    .text    .globl  main    .type   main, @functionmain:.LFB0:    .cfi_startproc    pushl   %ebp    .cfi_def_cfa_offset 8    .cfi_offset 5, -8    movl    %esp, %ebp    .cfi_def_cfa_register 5    andl    $-16, %esp    subl    $32, %esp    movl    $2, 20(%esp)    movl    $3, 24(%esp)    movl    24(%esp), %eax    movl    %eax, 4(%esp)    movl    20(%esp), %eax    movl    %eax, (%esp)    call    add     movl    %eax, 28(%esp)    movl    28(%esp), %eax    movl    %eax, 12(%esp)    movl    24(%esp), %eax    movl    %eax, 8(%esp)    movl    20(%esp), %eax    movl    %eax, 4(%esp)    movl    $.LC0, (%esp)    call    printf    leave    .cfi_restore 5    .cfi_def_cfa 4, 4    ret     .cfi_endproc.LFE0:    .size   main, .-main    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"    .section    .note.GNU-stack,"",@progbits

請不要問我上述代碼是什么意思!-_-

3.匯編(Assemble)

匯編過程將上一步的匯編代碼轉(zhuǎn)換成機(jī)器碼(machine code),這一步產(chǎn)生的文件叫做目標(biāo)文件,是二進(jìn)制格式。gcc匯編過程通過as命令完成:

$ as test.s -o test.o

等價(jià)于:

gcc -c test.s -o test.o

這一步會為每一個(gè)源文件產(chǎn)生一個(gè)目標(biāo)文件。因此mymath.c也需要產(chǎn)生一個(gè)mymath.o文件

4.鏈接(Linking)

鏈接過程將多個(gè)目標(biāo)文以及所需的庫文件(.so等)鏈接成最終的可執(zhí)行文件(executable file)。

命令大致如下:

$ ld -o test.out test.o inc/mymath.o ...libraries...

結(jié)語

經(jīng)過以上分析,我們發(fā)現(xiàn)編譯過程并不像想象的那么簡單,而是要經(jīng)過預(yù)處理、編譯、匯編、鏈接。盡管我們平時(shí)使用gcc命令的時(shí)候沒有關(guān)心中間結(jié)果,但每次程序的編譯都少不了這幾個(gè)步驟。也不用為上述繁瑣過程而煩惱,因?yàn)槟闳匀豢梢裕?/p>

$ gcc hello.c # 編譯$ ./a.out # 執(zhí)行

參考文獻(xiàn)

1.https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html 2.http://www.trilithium.com/johan/2005/08/linux-gate/ 3.https://gcc.gnu.org/onlinedocs/gccint/Collect2.html

責(zé)任編輯:

標(biāo)簽:

相關(guān)推薦:

精彩放送:

新聞聚焦
Top