Linux C/C++ 编译构建:GCC/G++ + Makefile 零基础完整教程

文章目录


⭕前言

前面我们重点为大家介绍了vim编辑器,它是一款轻量、高效的编辑器,可以为我们快速的谱写代码,而想要让一个文件变成一个可执行程序,还需要经过预处理、编译、汇编、链接 步骤,才能生成对应的可执行程序,才能跑起来。

那么,这些步骤都由谁来做呢?就是我们这篇文章的主角:gcc/g++ ,也就是说gcc/g++负责帮我们从一个编写好的程序 处理成为一个可执行程序

cpp 复制代码
//假设我们所编写的文件为main.c,想要将其可执行程序文件命名为main
gcc main.c -o mian//main就为我们所要执行的程序

🆙一、编译器gcc/g++

1.1背景知识

想要了解gcc/g++,就先要明白对文件进行预处理、编译、汇编、链接都干了些什么

①预处理

1.展开所有头文件
#include<stdio.h> #include"xxx.h"

把头文件中的所有代码原样复制 插到当前源码里,合并成一个文件
2.宏替换
# define MAX 100

代码中的MAX会被全部替换成100
3.处理条件编译
#ifdef、#ifndef、#else、#endif

满足条件的代码保留,不满足的直接删掉不参与后续编译
4.删除所有注释

单行注释 //、多行注释 /* */

预处理阶段全部清空,后面编译阶段看不到注释

预处理完成后,源码变成了无注释、头文件全部展开、宏全部替换完毕的 .i 文件,接下来就进入编译、汇编、链接阶段。

cpp 复制代码
//想要让文件编译成为.i文件,使用-E选项
gcc -E main.c -o main.i//mian.c是我们的文件,-E为形成对应.i文件的选项,里面为预处理后的文件内容

②编译

  • 编译阶段:把预处理后的 .i 文件,翻译成汇编代码
  • 进入编译的文件:main.i
  • 核心目的:1. 语法/语义检查 :检查变量未定义、类型不匹配、语法错误这些问题(大部分编译报错都出在这一步)2. 代码优化 :根据你加的-O/-O2等选项做优化 3. 生成对应平台的汇编指令(不同CPU架构,汇编指令不一样)
cpp 复制代码
//生成汇编语言.s的命令
gcc -S main.i -o main.s
//执行后main.s的文件内容就是纯汇编代码

③汇编(转成cpu能够识别的指令集)

  • 汇编阶段:把汇编代码翻译成机器指令(二进制),生成目标文件
  • 进入汇编的文件:main.s
  • 核心目的:1. 将汇编指令翻译成机器能直接执行的二进制指令 2. 生成「可重定位目标文件」(.o / .obj),里面包含机器指令和符号表,但还不能直接运行
cpp 复制代码
//生成可重定位目标文件.o的命令
gcc -c main.s -o main.o
//或者直接从.c跳转到.o的文件(跳过中间步骤)
gcc -c main.c -o main.o

④链接

  • 链接链接阶段:把多个目标文件和库文件合并,生成最终的可执行程序
  • 进入链接的文件:main.o + 其他.o文件 + 库文件(比如libc.so
  • 核心目的: 1. 符号解析 :把你代码里调用的函数(比如printf)和库文件里的定义关联起来 2. 重定位 :把目标文件里的地址修正为最终运行时的内存地址 3. 合并段:把多个目标文件的代码段、数据段合并成一个
cpp 复制代码
//生成可执行文件的命令
gcc main.o -o main
//执行后就可以./main运行程序了
cpp 复制代码
//main.c
#include<stdio.h>
#define MAX 100
int main()
{
	printf("我是 MAX: %d\n",MAX);
	return 0;
}
//1.预处理 gcc -E main.c -o main.i
//2.编译 gcc -S main.i -o main.s
//3.汇编 gcc -c main.s -o main.o
//4.链接 gcc -o main main.o

1.2动态链接和静态链接

在实际开发中,我们不会把所有代码都写在一个 .c 文件里,而是按功能拆分成多个源文件,比如 main.c、calc.c、utils.c。这些文件之间会互相调用函数,比如 main.c 里会用到 calc.c 里写好的加法函数。

当所有 .c 文件都编译成 .o 目标文件后,链接阶段的任务,就是把这些分散的目标文件 "拼" 成一个完整的可执行程序,解决它们之间的函数调用问题,让程序运行时能找到每一个函数的定义。
静态链接

编译链接时,会把你源文件中所用到的方法,从库中拷贝进我们的可执行程序

  • 结果:生成的文件体积更大,但它自带了所有依赖的库代码,拷贝到任何同架构的 Linux 机器上都能直接运行,不依赖系统环境。
  • 缺点:多个程序用同一个库时,每个程序都带一份库代码,会重复占用磁盘和内存空间。

动态链接

并不会把方法拷贝进可执行程序中,而是需要用到什么方法,记录下库的路径和符号,到指定库中查找即可

  • 结果:生成的 app_dynamic 文件体积很小,运行时系统会把共享库加载到内存里,多个程序可以共用同一份库代码,节省资源。
  • 缺点:程序运行时必须找到对应的系统库,如果环境里没有这个库(或者版本不对),程序就会报错跑不起来。

以读书举例:

动态链接就像是去图书中翻阅,而静态链接则是把整本书借走


🆒二、自动化构建-make/Makefile

2.1背景

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  • makefile带来的好处就是-自动化编译,一旦写好了makefile,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令
  • make是一个命令,makefile是一个文件,两者搭配使用,完成项目自动化构建

2.2基本使用

实例代码

cpp 复制代码
//假设这个代码存放在文件myproc.c中
#include<stdio.h>
int main()
{
	printf("hello makefile\n");
	return 0;
}

Makefile文件

cpp 复制代码
myproc:myproc.c
	gcc-o myproc myproc.c
.PHONY:clean
clean:
		rm -rf myproc

有了源文件和makefile,我们只要在命令行中输入make,系统就会执行gcc -o myproc myproc.c,当前目录下就会生成myproc文件;在命令行中输入make clean,系统就会执行rm -rf myprocmyproc文件就会被删除

  • 依赖关系
    上面的文件myproc,它依赖myproc.c,有了myproc.c才能生成myproc
  • 依赖方法
    gcc -o myproc myproc.c,就是与之对应的依赖关系
  • 项目清理
    ①工程是需要被清理的
    ②像clean,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令make clean,以此来清楚所有的目标文件,以便重编译
    ③但是一般我们这种clean的目标文件,我们将它设置为伪目标,用.PHONY修饰,伪目标的特性是,总是被执行的

2.3什么叫总是被执行

我随机罗列一个文件的属性,请看:

cpp 复制代码
  File: main.c
  Size: 215       	Blocks: 8          IO Block: 4096   regular file
Device: 805h/2053d	Inode: 263214     Links: 1
Access: (0644/-rw-r--r--)  UID: (1000/user)   GID: (1000/user)
Access: 2026-05-09 16:30:22.184257345 +0800
Modify: 2026-05-09 16:20:15.221356789 +0800
Change: 2026-05-09 16:20:15.221356789 +0800
 Birth: -
  • Access时间:常指的是文件最近一次被访问的时间。在linux早期版本,每当文件被访问时,其atime都会更新。但这种机制会导致大量的IO操作。导致效率低下。
  • Modify时间:记录最近一次文件内容变更的时间
  • Change时间:记录最近一次文件属性变更的时间

为什么改文件内容,Modify时间会改变(内容变了,Modify时间也会改变),Change时间也会改变???

这是因为修改内容,文件大小会发生变化,文件大小也是属性,属性一旦发生变化,Change时间也随之发生变化

知识铺垫完之后,我们再来看看

myproc:myproc.c举例。

我们这里的目标文件myproc是依赖源文件myproc.c生成的,假设我们的myproc.c没发生改变,我们还需要再次生成一个myproc吗?

①显然是不需要的,你myproc.c都没发生改变,那么你通过gcc编译后的myproc也必定没有发生改变,那我还要进行make生成它干嘛?浪费资源罢了,所以再次进行make系统会报错,不让你这么干!

②也就是说,myproc.c里的内容改变了,make之后重新生成的myproc才有意义。也就是说myproc.c的Modify时间如果比myproc的Modify时间早 (说明在make之后,myproc.c无修改),那么make就无意义;myproc.c的Modify时间如果比myproc的Modify时间晚(说明在make之后,myproc.c做了修改),那么make就有意义

本质:比较的是两者Modify时间的前后

结论
①源文件是否需要被重新编译?源文件和可执行谁更新?看两个文件的修改时间
②判断文件新旧,根据文件Mod时间判断的!

所以,.PHONY的本质就是忽略Mod时间新旧的对比

如果在myproc前面加上.PHONY的话,也就是忽略myproc和myproc.c的新旧时间对比,系统根本不检查、不对比内容有没有发生改变,强制重新编译 ,没必要!

如果不加.PHONY,系统会检查对比时间,没变化的话直接跳过编译提升效率

举个例子:

书包收拾好了,明天东西没变动,不用重新检查(不加.PHONY)

不管书包收没收拾好,强制整理一遍(加了.PHONY)

2.4推导过程

Makefile文件

cpp 复制代码
myproc:myproc.o
	gcc myproc.o -o myproc
myproc.o:myproc.s
	gcc -c myproc.s -o myproc.o
myproc.s:myproc.i
	gcc -S myproc.i -o myproc.s
myproc.i:myproc.c
	gcc -E myproc.c -o myproc.i
	
.PHONY:clean
clean:
		rm -rf *.i *.s *.o myproc

推导:

myproc依赖于myproc.o

myproc.o在哪呢?

myproc.o依赖于myproc.s

myproc.s在哪呢?

myproc.s依赖于myproc.i

myproc.i在哪呢?

myproc.i依赖于myproc.c
myproc.c就是我们所写的源文件

编译

cpp 复制代码
$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.i -o myproc

2.5拓展语法

cpp 复制代码
CC=gcc          # 编译器
BIN=myproc      # 最终生成的可执行文件
RM=rm -rf       # 删除命令
SRC=$(wildcard *.c)  # 匹配当前目录所有 .c 文件
OBJ=$(SRC:.c=.o)     # 将 .c 文件替换为 .o 目标文件
符号 含义
*.c 匹配所有 .c 后缀文件
$@ 规则中的目标文件名
$^ 规则中的所有依赖文件
$< 规则中的第一个依赖文件
%.o:%.c 模式规则:所有 .o 文件依赖同名 .c 文件
cpp 复制代码
# 1. 链接生成可执行文件(最终目标)
$(BIN):$(OBJ)
	$(CC) -o $@ $^  # 编译命令,必须以 Tab 开头!

# 2. 编译 .c 生成 .o 目标文件
%.o:%.c
	$(CC) -c $<

# 3. 清理编译产物
clean:
	$(RM) $(OBJ) $(BIN)

# 声明伪目标,避免和同名文件冲突
.PHONY:clean

🅱️总结

我个人认为gcc/g++没有什么东西可以学的,因为底层工作上层已经帮我们完成了,知道怎么用和ESc选项生成对应.i,.s,.o文件即可;至于makefile,很重要,多接触,多敲吧。

相关推荐
风曦Kisaki1 小时前
#Linux Shell 编程入门 Day05 :awk文本数据处理基础
linux·运维
Maguyusi1 小时前
Ubuntu26.04 编译 abseil-cpp protobuf v33.6
linux·protobuf·abseil
念恒123061 小时前
基础IO(文件缓冲区)
linux·c语言·c++
吃着火锅x唱着歌1 小时前
LeetCode 726.原子的数量
linux·算法·leetcode
君义_noip1 小时前
CSP-S 2025 提高级 第一轮(初赛) 阅读程序(3)
c++·算法·信息学奥赛·csp-s 初赛
剑神一笑1 小时前
Linux tree 命令深度解析:从目录遍历到树形可视化的完整实现
linux·运维·elasticsearch
汉克老师1 小时前
GESP6级C++考试语法知识(三、图与树(三))
c++·中序遍历·bst·完全二叉树·二叉排序树·gesp6级·gesp六级
啊罗罗1 小时前
windows下,c++的axv2+fma/avx-vnni加速计算demo
c++·windows·算法
li星野2 小时前
滑动窗口五题通关:从最小覆盖子串到水果成篮(Python + C++)
c++·python·学习