1. 为什么要编译
c语言是一门高级语言,需要编译器将其转换成计算机能理解的机器语言,才能在计算机上执行。编译的过程就是将c语言代码转换成汇编代码文件的过程。
2. 编译过程
程序从代码编译成可执行文件或者库需要经历预处理,编译,汇编和链接四个阶段.这四个阶段如同工厂流线一样对代码进行一道有一道无情的加工.如下图:
2.1. 预处理:
预处理主要将代码中的注释删除。替换使用#defined指令的常量值和表达式的宏定义。将#include指令包含的文件中的内容添加到当前文件中,最终生成.i
文件。
gcc -E main.c -o main.i
-E 让gcc在预处理结束后停止编译
-o 执行目标文件
.i文件是完成预处理过程的c语言原始文件
2.2. 编译
将源码转换成机器语言。这一步将.i
临时文件转换为具有汇编级指令的汇编文件.s
文件。
注意此步依然是代码,只不过转换成汇编代码。因为涉及c语言转换成汇编语言,所以该步可以检测出源代码终存在的语言错误或警告。
gcc -S main.i -o main.s
-S 让gcc只编译不进行汇编
.s 汇编文件
2.3. 汇编
将汇编程序代码.s
文件转换成机器可理解的代码。对应的文件扩展名为.o
这一步生成的的机器代码,是计算机可理解的代码。由于此步骤生成的机器代码相互独立,而一个程序中由多个机器代码相互依赖,所以该步骤程序依然不能独立运行,需要经过下一步链接。
gcc -c main.s -o main.o
2.4. 链接
将汇编的二进制目标代码链接起来,生成可执行文件或库文件,也会自动链接系统库函数,如printf
函数,在stdio.h
中定义,但是在libc.so.6
中实现,会链接该库.
gcc -o main main.o
3. 实验验证
3.1 首先建立三个文件hello.c hello.h main.c
分别写入如下内容
/// hello.h
#pragma once
#define NAME "xiangli"
const ConstValue = 123;
void func();
/// hello.c
void func(){
return;
}
/// main.c
#include "hello.h"
#include "stdio.h"
int main(int argc, const char** argv) {
printf("hello world %s", NAME);
func();
return 0;
}
3.2 验证预处理过程:
查看头文件是否包含,宏定义是否替换
```makefile
# 执行
gcc -E main.c -o main.i
gcc -E hello.c -o hello.i
执行上面两条命令,如下图,可以看见文件夹中生成了两个新文件hello.i
和main.i
。其中main.i
内容比main.c
文件多了很多内容,也包含了hello.h中的内容。这就是预处理将include文件添加到当前当前文件中。
3.3 验证编译过程:
查看编译后生成的汇编代码
# 执行
gcc -S main.i -o main.s
gcc -S hello.i -o hello.s
执行完上面两条命令,文件夹中新增了两个.s
后缀的文件 ,这便是汇编代码的程序。这一步会对代码语法进行检查并进行提示错误或告警。
小插曲:上述代码hello.h
文件中常量没有指定类型const ConstValue = 123;
,定义错误在此步骤进行了告警提示。改正后,继续进行下面实验。
3.4 验证汇编过程:
# 执行
gcc -c main.s -o main.o
gcc -c hello.s -o hello.o
执行完上面代码,生成两个.o
文件。该文件便是执行汇编后生成的二进制文件。
3.5 验证链接过程:
# 执行
gcc -o main main.o hello.o
执行完上面代码,文件夹中生成main.exe
文件,该文件是链接汇编中两个.o
文件和库文件生成的可执行文件。可在计算机中直接运行。
至此,c/c++程序编译部分写完了,由于能力有限,肯定存在错误和不足,欢迎留言指正。后面我将会介绍make, cmake,gcc,gdb等编译调试工具在程序编译中的作用,以及vscode
如何编译调试c/c++程序,欢迎关注。