本文从一个简单的例子来说明我们写的代码是怎么办成可执行程序的
#include
int main()
{
printf("hello world\n");
return 0;
}
以上是一个最简单的c程序源码
这个源码是怎么办成可执行程序呢
平时使用IDE开发过程中我们只需要写好源码文件,点击运行或按下F5即可,但这看似简单的操作背后隐藏了一系列不可或缺的过程。
一个c程序从源码到可执行程序需要经过 预处理 编译 汇编 链接 这几个阶段
预处理阶段
预处理器会分析源程序,解析其中的预处理指令(#开头的指令)
使用gcc -E参数[1]可以查看预处理后的结果
-E Preprocess only; do not compile, assemble or link.
执行gcc -E hello.c > hello.i我们发现输出的文件中上部分是stdio.h头文件内容,下面是我们的源码文件
...省略...
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 868 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main(){
printf("hello world\n");
return 0;
}
编译阶段
编译器会把上阶段展开后的源码文件翻译成汇编文件
使用gcc -S参数可以查看编译后的结果
-S Compile only; do not assemble or link.
执行 gcc -S hello.i -o hello.s将会生成hello.s汇编语言文件
1 .file "hello.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 pushq %rbp
13 .cfi_def_cfa_offset 16
14 .cfi_offset 6, -16
15 movq %rsp, %rbp
16 .cfi_def_cfa_register 6
17 leaq .LC0(%rip), %rdi
18 call puts@PLT
19 movl $0, %eax
20 popq %rbp
21 .cfi_def_cfa 7, 8
22 ret
23 .cfi_endproc
24 .LFE0:
25 .size main, .-main
26 .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
27 .section .note.GNU-stack,"",@progbits
汇编阶段
将汇编语言文件生成relocatable的目标文件
通过gcc -c参数可以实现编译,汇编不链接
-c Compile and assemble, but do not link.
我们执行gcc -c hello.c后生成hello.o文件
通过file hello.o命令可以查看该文件格式为
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
链接阶段
最后由链接器将生成的.o文件和其它引用的.o文件一起链接成一个可执行程序 hello
-
gcc预处理参数 ↩