Linux下gcc编译C程序过程
gcc编译c语言程序过程分为4个步骤:
- 1.预处理
通过 -E 命令,生成 . i 文件。 - 2.编译
通过 -S 命令,将 . i 文件转换成成 . s 文件。 - 3.汇编
通过 -c 命令,将 . s 文件生成 . o 文件 - 4.链接
再通过 -o 命令将各个文件链接生成可执行文件(无后缀)。
-o 命令把输出文件输出到指定文件里
以一个 test.c 为例子,输出hello world。
使用cat命令查看文件内容,-n表示显示函数。
cat -n test.c
结果:
1 #include <stdio.h>
2 int main()
3 {
4 printf("hello world!\n"); //输出hello world!
5 return 0;
6 }
通常编译的时候,只需一步即可编译链接生成可执行文件再运行:
方法1:
gcc test.c -o test
./test
上面编译链接过程中的test.c和 -o test可以换位置,结果一样。
方法2:
gcc test.c
./a.out
gcc test.i或gcc test.o或gcc test.s都会生成默认可执行文件a.out
预处理
为编译做预备工作,将源程序中含有#的代码替换掉。
比如在源程序中的#include <stdio.h>命令告诉预处理器读取系统头文件<stdio.h>的内容,并把它直接插入到程序文本中。(注意头文件的内容只有声明,没有相关代码的实现,实现的代码在链接阶段获取)
输入命令:
gcc -E test.c -o test.i
如果不加 -o test.i 则表示直接进行预处理并且显示出结果,不会生成 . i 文件。
结果为:
1 # 1 "test.c"
2 # 1 "<built-in>"
3 # 1 "<command-line>"
4 # 31 "<command-line>"
5 # 1 "/usr/include/stdc-predef.h" 1 3 4
6 # 32 "<command-line>" 2
7 # 1 "test.c"
8 # 1 "/usr/include/stdio.h" 1 3 4
9 # 27 "/usr/include/stdio.h" 3 4
10 # 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
11 # 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
12 # 1 "/usr/include/features.h" 1 3 4
13 # 461 "/usr/include/features.h" 3 4
14 # 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
15 # 452 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
16 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
17 # 453 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
18 # 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4
19 # 454 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
20 # 462 "/usr/include/features.h" 2 3 4
....(此处省略,都是将stdio.h展开成宏等,头文件中只有声明,没有定义,定义需要在链接阶段去找)
....
719 extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
720 # 858 "/usr/include/stdio.h" 3 4
721 extern int __uflow (FILE *);
722 extern int __overflow (FILE *, int);
723 # 873 "/usr/include/stdio.h" 3 4
724
725 # 2 "test.c" 2
726
727 # 2 "test.c"
728 int main()
729 {
730 printf("hello world!\n");
731 return 0;
732 }
编译
将源程序语言转换成汇编语言,生成 . o 文件。
输入命令:
gcc -S test.i -o test.s
此处的 -o test.s 可以去掉,不加也会自动生成test.s文件
结果:
1 .file "test.c"
2 .text
3 .section .rodata
4 .LC0:
5 .string "hello world!"
6 .text
7 .globl main
8 .type main, @function
9 main:
10 .LFB0:
11 .cfi_startproc
12 endbr64
13 pushq %rbp
14 .cfi_def_cfa_offset 16
15 .cfi_offset 6, -16
16 movq %rsp, %rbp
17 .cfi_def_cfa_register 6
18 leaq .LC0(%rip), %rdi
19 call [email protected]
20 movl $0, %eax
21 popq %rbp
22 .cfi_def_cfa 7, 8
23 ret
24 .cfi_endproc
25 .LFE0:
26 .size main, .-main
27 .ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
28 .section .note.GNU-stack,"",@progbits
29 .section .note.gnu.property,"a"
30 .align 8
31 .long 1f - 0f
32 .long 4f - 1f
33 .long 5
34 0:
35 .string "GNU"
36 1:
37 .align 8
38 .long 0xc0000002
39 .long 3f - 2f
40 2:
41 .long 0x3
42 3:
43 .align 8
44 4:
汇编
将汇编语言转换成机器语言,生成 . s 文件。
gcc -c test.s -o test.o
此处的 -o test.o可以去掉,不加也会自动生成test.o文件
此处用cat打开test.o可能回乱码,所以使用以下命令:
hexdump test.o
结果:
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0001 003e 0001 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0310 0000 0000 0000
0000030 0000 0000 0040 0000 0000 0040 000e 000d
0000040 0ff3 fa1e 4855 e589 8d48 003d 0000 e800
0000050 0000 0000 00b8 0000 5d00 68c3 6c65 6f6c
0000060 7720 726f 646c 0021 4700 4343 203a 5528
0000070 7562 746e 2075 2e39 2e33 2d30 3731 6275
0000080 6e75 7574 7e31 3032 302e 2934 3920 332e
0000090 302e 0000 0000 0000 0004 0000 0010 0000
00000a0 0005 0000 4e47 0055 0002 c000 0004 0000
00000b0 0003 0000 0000 0000 0014 0000 0000 0000
00000c0 7a01 0052 7801 0110 0c1b 0807 0190 0000
00000d0 001c 0000 001c 0000 0000 0000 001b 0000
00000e0 4500 100e 0286 0d43 5206 070c 0008 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0001 0000 0004 fff1
0000110 0000 0000 0000 0000 0000 0000 0000 0000
0000120 0000 0000 0003 0001 0000 0000 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0003 0003
....
....
无论是windows还是linux,为了方便人类观察,计算机上的二进制数都用十六进制来表示,生成的都是十六进制代码,转化成二进制很方便,所以就等价于生成机器代码。
链接
这个阶段将有关的目标文件彼此相连接,也就是把可执行程序需要的所有的编译过程产生的.o文件组合到一起。比如gcc会在lib文件(实际上就算打包的.o文件集合)寻找函数实现的定义。
输入命令:
gcc test.o -o test
运行
有两种可执行文件,一种是默认生成的a.out(历史遗留原因),一种无后缀的可执行文件,运行哪个结果都一样。
输入命令:
./test.c
结果:
hello world!
下一篇: Python爬虫05-bs4