在windows系统中我们平常使用的都是已经集成化的编译软件,但是大家并不知道c语言在实际的编译过程是怎样的,在Linux系统中写了多个文件是如何进行编译的。其实windows系统下的编译软件的原理就是Makefile。
在初学Linux时,我们通过gcc -Wall hello.c -o hello,来生成一个可执行文件。但是在实际编译的时候,这个过程经历了4个阶段:预编译、编译、汇编、链接。
预编译(Preprocessing)是程序编译过程中的一个阶段,它在编译器对源代码进行实际编译之前进行。预编译过程主要由预处理器(Preprocessor)负责执行,它根据预处理指令对源代码进行处理,并生成一个新的被处理过的代码文件。在gcc后面添加-E选项,生成一个.i文件,接下来进行编译。
gcc -E hello.c -o hello.i
编译可以将C语言代码转换为汇编代码的过程。编译器(Compiler)将源代码进行语法分析和语义检查,并将其转换为汇编语言或直接生成机器码。通过-S选项,根据.i文件生成.s文件。
gcc -S hello.i -o hello.s
汇编语言是一种与机器指令一一对应的书写和理解较容易的语言。汇编器(Assembler)将汇编语言代码转换为机器指令的二进制表示形式。通过-c选项,gcc编译器便会根据.s文件生成一个计算机可识别的.o文件。
gcc -c hello.s -o hello
其实上三步也可以将其合并起来,直接输入gcc -c hello.c -o hello,即可一步生成可执行文件。因为hello.c是一个单独的文件,不需要链接,所以不执行链接这一步。那么在如果当前目录有如下文件:main.c fun1.c fun2.c myinclude.h就需要链接了
链接是将多个目标文件和库文件合并为一个可执行文件的过程。在编译时,多个源文件会被分别编译为独立的目标文件。链接器(Linker)负责将这些目标文件中的符号引用和定义进行解析和连接,生成最终的可执行文件。
//main.c
#include"myhead.h"
int main(){
function1();
function2();
return 0;
}
//fun1.c
#include"myhead.h"
void function1(){
printf("This is funtion one\n");
}
//fun2.c
#include"myhead.h"
void function2(){
printf("This is funtion two\n");
}
//myhead.h
#ifndef _MYHEAD_H
#include<stdio.h>
void function1();
void function2();
#endif
这是除了头文件不需要编译,main.c fun1.c fun2.c文件都需要gcc -c 生成一个可执行文件,然后再链接gcc main.o fun1.o fun2.o -o test。这样编译就会非常麻烦,每次编译调试,都需要去生成.o文件,然后再去链接。因此我们可以通过Makefile去帮我们执行整个编译的四个完整过程。
Makefile 是一种用于自动化构建和管理程序的文件,它定义了一组规则和命令,用于构建、编译和链接源代码以生成可执行文件、库文件或其他目标文件。我们可以在Makefile中定义一些变量,用于存储编译器、编译选项、源文件列表、目标文件名等信息。通过使用变量,可以方便地在整个 Makefile 文件中重复使用和修改这些值。根据编译和链接的规则,以及依赖关系。每个规则包含一个目标文件和其依赖项,以及用于生成目标文件的命令。Makefile 使用这些规则来判断哪些文件需要重新编译。就比如说gcc -c hello.c -o hello,其中要生成的文件hello.i就是目标文件,而它是依赖于hello.c文件生成的,所以hello在这一个过程中是依赖文件。
hello:hello.c //文件依赖关系
gcc -c hello.c hello //编译命令
这只是单个文件编译,那么在编译之前所说的test文件包含两个函数,一个头文件和一个主函数,我们也不需要去逐个编译,下面介绍三个自动变量。$@用于指代造成命令运行的目标,$^表示一个文件依赖关系中的所有依赖文件,$<依赖关系中的第一个依赖文件。
test:main.o fun1.o fun2.o // test的依赖关系
gcc $^ -o $@ // gcc main.o fun1.o fun2.o -o test
%.o:%.c // %.o通配符,指代所有要生成的.o目标文件
gcc -c $< -o $@ // gcc -c main.c -o main.o
// gcc -c fun1.c -o fun1.o
// gcc -c fun2.c -o fun2.o
先提醒一下,gcc的前面是tab键,而不是空格。通过自动变量的替换,可以简化写Makefile的步骤,这样一个生成test的Makefile文件就写好了,然后只需要在文件所在目录输入make命令即可生成test可执行文件。如果哪一个文件有改动,make工具会自动识别该文件是否被更新或更改,然后只从新编译新的文件,再链接即可重新编译test文件。
linux@ubuntu:~/test/maketest$ make
gcc -c main.c -o main.o
gcc -c fun1.c -o fun1.o
gcc -c fun2.c -o fun2.o
gcc main.o fun1.o fun2.o -o test
linux@ubuntu:~/test/maketest$ ./test
This is funtion one
This is funtion two
linux@ubuntu:~/test/maketest$