分析hello,world的分析——编译、链接的过程(cc编译指令,编译依赖平台的工具链)
文件:hello.c
#include<stdio.h>
#ifndef NUM
#define NUM 100 //宏定义,就是NUM代表100
#endif
#ifdef NUM
#define HEHE "hehe\n"
#endif
int main(){
printf("hello,world!\n");
printf(HEHE);
return 0;
}
现在通过编译指令来分析一下这段最简单的代码:
编译一般分为下面的几个步骤:预处理——编译为汇编代码——汇编——连接(连接之前的操作一般被称为编译)
下面来逐步的分析这些过程,解析hello world的整个过程:
在开始之前,先来说明一个问题——我们最开始写的是c语言文件,最后生成的是连接得到的机器指令执行文件(以二进制存储在磁盘),因为电脑只能执行二进制机器指令。二进制机器指令十分复杂的,很难学会,所以发明了汇编语言,由汇编语言文件编译(一般叫做汇编)得到机器指令文件,汇编语言的学习不是那么复杂,但是学起来也很难,因此出现了c语言,由c语言编译生成汇编语言。
第一步、预处理(指令)——gcc -E hello.c -o test.i(-E代表进行预处理操作,-o左边为执行文件,右边为目的文件,o代表object)
查看结果指令(cat hello.i):
...(太多了,不写了)
extern voidfunlockfile (FILE *__stream) __attribute__ ((__nothrow__));
#936"/usr/include/stdio.h" 3 4
#2"hello.c" 2
int main(){
printf("hello,world 100!\n");
printf("hehe\n");
return 0;
}
从这里可以看出它的目的——把stdio.h头文件的内容移到这里代替#include<stdio.h>的位置;把下面的NUM与HEHE进行替换;(进行宏定义与头文件的替换)——此外,还会去除注释,添加行号和文件标识(这是为了在后面的编译时如果出现错误,可以及时的打印错误信息位置)
当然,这里#include<stdio.h>里面很多东西用不到,但是这一步只是替换,不会去分析是否用的到
预处理作用总结:
- 将头文件替换为它的具体内容
- 将#define的宏定义替换
- 去除注释
- 添加行号,文件标识————为了调试以及运行出错时报错
第二步、编译为汇编代码指令——cc -S hello.i -ohello.s <将c语言编译为汇编语言代码>
查看结果指令(cat hello.s):
.file "hello.c"
.section .rodata
.LC0:
.string"hello,world!"
.LC1:
.string"hehe"
.text
.globlmain
.typemain,@function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset16
movq %rsp,%rbp
.cfi_offset6, -16
.cfi_def_cfa_register6
movl $.LC0,%edi
call puts
movl $.LC1,%edi
call puts
movl $0,%eax
leave
.cfi_def_cfa7, 8
ret
.cfi_endproc
.LFE0:
.size main,.-main
.ident"GCC:(Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
.section.note.GNU-stack,"",@progbits
在这里暂时不去关注汇编语言的含义,但是可以看出,代码被缩减了,#include<stdio.h>里面只有用到的被汇编了,其他的都没有用。
第三步、汇编指令——cc -c hello.s -o hello.o <将汇编代码编译为机器指令>
生成的文件内容这里就不贴上来了,但是使用cat查询会出现混乱,这是因为机器指令不是字符状态,而是二进制状态,而cat查看是以字符方式打开的,可以使用二进制(十六进制等)指令查看,不过只是一堆数据,根本看不懂~~~
第四步、连接指令——cc hello.o -o hello
连接的目的是得到可执行文件,通过与静态连接库(c语言连接库)与动态连接库(添加的头文件库与自定义头文件库)的连接,生成可执行文件。<链接库是机器指令的文件,因此在这一步实现链接——如printf操作的具体实现就是在这一步链接的>
注意:如果不添加#include<stdio.h>,仍然可以连接,这是因为静态连接库里面包含了printf这个东西,
到此得到可执行文件hello,当然可以直接使用指令(cc hello.c -o hello)完成全部操作
总结:
- 预处理——仅仅把#include与#define做替换,代码错了也会不管的
- 编译为汇编代码——由c语言到汇编,在变为汇编时会检查代码语法是否错了,没有定义需要的动态连接库也会报错
- 汇编——将汇编语言变为机器指令,只要上一步没错,这一步不会出错
- 连接——把所有.o文件与c基础连接库连接生成可执行文件