gcc 编译工具(上)— 编译过程和原理浅析
预处理(Pre-Processing):主要包括宏定义,文件包含,条件编译三部分。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对其进行响应和替换。预处理过程还会删除程序中的注释和多余空白字符。最后会生成 .i 文件。
编译器(Compiling):编译器会将预处理完的 .i 文件进行一些列的语法分析,并优化后生成对应的汇编代码。会生成 .s 文件。
汇编器(Assembling):汇编器会将编译器生成的 .s 汇编程序汇编为机器语言或指令,也就是可以机器可以执行的二进制程序。会生成 .o 文件。
链接器(Linking):链接器会来链接程序运行的所需要的目标文件,以及依赖的库文件,最后生成可执行文件,以二进制形式存储在磁盘中。
4. gcc编译过程
4.1 gcc常用选项
-o:生成目标( .i 、 .s 、 .o 、可执行文件等)
-c:通知 gcc 取消链接步骤,即编译源码并在最后生成目标文件。
-E:只运行 C 预编译器
-S:告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为 .s
-Wall:使 gcc 对源文件的代码有问题的地方发出警告
-Idir:将dir目录加入搜索头文件的目录路径
-Ldir:将dir目录加入搜索库的目录路径
-llib:连接lib库
-g:在目标文件中嵌入调试信息,以便gdb之类的调试程序调试
4.2 预处理过程
我们以 hello.c 程序为例:
#include
#define HELLOWORLD (“hello world\n”)
int main(void)
{
printf(HELLOWORLD);
return 0;
}
1
2
3
4
5
6
7
8
9
10
使用gcc -E hello.c -o hello.i命令,将 hello.c 文件预处理并且生成 hello.i 目标文件。
之前说道,预处理会将头文件包含进来并且会将宏定义进行替换,因此替换后的 hello.i 文件如下:
…
…
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
…
…
extern struct _IO_FILE *stdin;
extern struct _IO_FILE *stdout;
extern struct _IO_FILE *stderr;
extern FILE *fopen (const char *__restrict __filename,
const char *__restrict __modes) ;
…
…
int main(void)
{
printf((“hello world\n”));
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
可以看到将 stdio.h 文件包含进来,并且原封不动的将 HELLOWORLD 宏进行了替换。
4.3 编译过程
使用gcc -S hello.i -o hello.s,将生成的hello.i文件编译为汇编程序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
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident “GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609”
.section .note.GNU-stack,"",@progbits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
可以看到hello.s文件中全部都是汇编指令,说明已经生成成功了。
4.4 汇编过程
汇编就是要将hello.s文件中的汇编指令全部转换为二进制的机器指令。
执行gcc -c hello.s -o hello.o命令。而生成的hello.o文件是二进制文件,我们用od -b hello.o命令看一下该二进制文件的八进制表示。
➜ test od -b -w8 hello.o
0000000 177 105 114 106 002 001 001 000
0000010 000 000 000 000 000 000 000 000
0000020 001 000 076 000 001 000 000 000
0000030 000 000 000 000 000 000 000 000
…
0002710 000 000 000 000 000 000 000 000
0002720 001 000 000 000 000 000 000 000
0002730 000 000 000 000 000 000 000 000
0002740
// -b:八进制形式,1字节为1部分。
// -w8:每行显式8字节。
1
2
3
4
5
6
7
8
9
10
11
12
4.5 链接过程
链接hello.o程序运行的所需要的目标文件,以及依赖的库文件,最后生成可执行文件。
执行gcc hello.o -o hello不需要选项,生成hello二进制的可执行文件。同样可以使用od命令来查看。执行hello文件:
➜ test ./hello
hello world
1
2
以上编译过程的分步骤进行,还可以直接执行gcc hello.c -o hello直接生成可执行文件。
至此,使用gcc编译程序的过程就介绍完毕。
int main(void)
{
print(“hello world”);
return 0;
}
1
2
3
4
5
6
7
hello.c
#include “hello.h”
void print(const char *str)
{
printf("%s\n", str);
}
1
2
3
4
5
6
hello.h
#ifndef _HELLO_H
#define _HELLO_H
#include
void print(const char *str);
#endif
1
2
3
4
5
6
7
8
5.1 一次性编译
执行gcc -Wall hello.c main.c -o main命令,直接生成可执行文件main
➜ test gcc -Wall hello.c main.c -o main
➜ test ./main
hello world
1
2
3
5.2 独立编译
先分别将main.c和hello.c编译生成main.o和hello.o文件。然后将两个.o文件链接生成可执行文件newmain。
➜ test gcc -Wall -c main.c -o main.o
➜ test gcc -Wall -c hello.c -o hello.o
➜ test gcc -Wall hello.o main.o -o newmain
➜ test ./newmain
hello world
1
2
3
4
5
独立编译的好处就是,如果我们的代码发生更改,只需要独立编译更改的文件,最后在一起链接生成可执行文件。
————————————————
版权声明:本文为CSDN博主「men_wen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/men_wen/article/details/75200430