飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 基础入门

    • Linux 命令概述与环境准备
    • 文件与目录操作(上):ls、cd、pwd
    • 文件与目录操作(下):cat、touch、mkdir、rm、cp、mv
    • 获取帮助与基础工具:man、help、clear、exit、sudo
  • 文件权限

    • 文件权限管理:chmod、chown
  • 系统与监控

    • 进程管理与系统监控:ps、top、kill、df、du、free
    • 用户与环境:whoami、id、env、echo、history
  • 网络命令

    • 网络诊断(上):ping、curl、wget
    • 网络诊断(下):netstat、ss、ip
    • 网络抓包与高级工具:traceroute、tcpdump、nc
    • DNS 与防火墙:nslookup、dig、iptables
  • 文本处理

    • 文本搜索:grep
    • 文本处理(上):awk
    • 文本处理(下):sed
    • 文本排序与统计:sort、uniq、wc
    • 文件查看与查找:head、tail、cut、find
    • 归档压缩与管道组合:tar、gzip、xargs
  • 开发工具

    • 开发工具链(上):gcc、make
    • 开发工具链(下):gdb、time
    • 版本控制:git
    • 远程操作:ssh、scp
    • 编辑器:vim、nano
  • Shell 编程

    • Shell 编程基础(上):变量、管道、重定向
    • Shell 编程基础(下):条件、循环、函数
  • 综合实战

    • 综合实战:日志分析

开发工具链(上):gcc、make

gcc:GNU C 编译器

gcc(GNU Compiler Collection)是 Linux 下最常用的 C/C++ 编译器,也是计算机专业学生必须掌握的工具。

基础编译

# 编译单个源文件,生成可执行文件 a.out
gcc hello.c

# 指定输出文件名(-o output)
gcc hello.c -o hello

# 运行
./hello

编译四阶段

C 程序编译分为四个阶段:预处理 → 编译 → 汇编 → 链接。

# 只预处理(-E)
gcc -E hello.c -o hello.i

# 预处理并编译成汇编(-S)
gcc -S hello.c -o hello.s

# 编译成目标文件(-c,不链接)
gcc -c hello.c -o hello.o

# 链接目标文件生成可执行文件
gcc hello.o -o hello

# 一步到位(默认执行全部四阶段)
gcc hello.c -o hello

常用编译选项

选项作用示例
-o指定输出文件名gcc hello.c -o hello
-c只编译不链接gcc -c hello.c
-S只编译到汇编gcc -S hello.c
-E只预处理gcc -E hello.c
-g生成调试信息gcc -g hello.c -o hello
-Wall开启所有警告gcc -Wall hello.c
-Werror警告视为错误gcc -Werror hello.c
-O0不优化(默认)gcc -O0 hello.c
-O2优化级别 2gcc -O2 hello.c
-O3优化级别 3gcc -O3 hello.c
-I指定头文件搜索路径gcc -I./include hello.c
-L指定库文件搜索路径gcc -L./lib hello.c
-l链接指定库gcc hello.c -lm
-static静态链接gcc -static hello.c
-D定义宏gcc -DDEBUG hello.c
-U取消定义宏gcc -UNDEBUG hello.c
-v显示编译过程gcc -v hello.c
--save-temps保存中间文件gcc --save-temps hello.c

多文件编译

# 编译多个源文件
gcc main.c utils.c -o program

# 分别编译,再链接(推荐,只改一个文件时不用全编译)
gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
gcc main.o utils.o -o program

# 修改 utils.c 后,只需重新编译 utils.c 和链接
gcc -c utils.c -o utils.o
gcc main.o utils.o -o program

make:自动化构建工具

手动编译多文件项目很繁琐,make 通过 Makefile 定义编译规则,自动判断哪些文件需要重新编译。

Makefile 基础结构

# 目标: 依赖
# [Tab]命令

program: main.o utils.o
	gcc main.o utils.o -o program

main.o: main.c utils.h
	gcc -c main.c -o main.o

utils.o: utils.c utils.h
	gcc -c utils.c -o utils.o

clean:
	rm -f *.o program

注意:命令前必须是 Tab 字符,不能是空格。

make 工作原理

  1. 检查目标文件是否存在
  2. 检查依赖文件是否比目标文件新
  3. 如果依赖更新,执行命令重新生成目标
  4. 递归检查所有依赖
# 编译(默认执行第一个目标)
make

# 执行指定目标
make clean

# 显示要执行的命令但不执行(-n dry-run)
make -n

# 忽略错误继续执行(-k keep-going)
make -k

# 并行编译(-j jobs)
make -j4

变量和自动变量

# 定义变量
CC = gcc
CFLAGS = -Wall -g -O2
TARGET = program
SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o)

# 使用变量
$(TARGET): $(OBJS)
	$(CC) $(OBJS) -o $(TARGET)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean

自动变量

变量含义示例
$@目标文件名program
$<第一个依赖文件名main.c
$^所有依赖文件名(去重)main.c utils.c
$+所有依赖文件名(保留重复)main.c utils.c main.c
$?比目标新的依赖文件utils.c
$*目标的主干名(不含后缀)main

模式规则

# 通用编译规则:所有 .o 文件都依赖对应的 .c 文件
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 等价于:
# main.o: main.c
# 	$(CC) $(CFLAGS) -c main.c -o main.o
# utils.o: utils.c
# 	$(CC) $(CFLAGS) -c utils.c -o utils.o

本篇小结

  • gcc:GNU C 编译器,编译四阶段:预处理(-E)→ 编译(-S)→ 汇编(-c)→ 链接
  • -o 指定输出,-g 生成调试信息,-Wall 开启所有警告,-O2 推荐优化级别
  • -I 头文件路径,-L 库文件路径,-l 链接库(如 -lm 数学库)
  • make:自动化构建工具,通过 Makefile 定义目标和依赖关系
  • Makefile 结构:目标: 依赖 [换行][Tab]命令
  • 自动变量:$@ 目标,$< 第一个依赖,$^ 所有依赖,$? 更新的依赖
  • 模式规则:%.o: %.c 通用编译规则

动手实践

  1. 单文件编译:

    cat > hello.c
    #include <stdio.h>
    int main() {
        printf("Hello, GCC!\n");
        return 0;
    }
    # Ctrl+D
    
    gcc hello.c -o hello
    ./hello
    
  2. 查看编译四阶段:

    gcc -E hello.c -o hello.i    # 预处理
    gcc -S hello.c -o hello.s    # 编译到汇编
    gcc -c hello.c -o hello.o    # 编译到目标文件
    gcc hello.o -o hello         # 链接
    ls -lh hello.*
    
  3. 多文件项目:

    cat > utils.c
    #include <stdio.h>
    void greet(const char* name) {
        printf("Hello, %s!\n", name);
    }
    # Ctrl+D
    
    cat > utils.h
    void greet(const char* name);
    # Ctrl+D
    
    cat > main.c
    #include "utils.h"
    int main() {
        greet("Linux");
        return 0;
    }
    # Ctrl+D
    
    cat > Makefile
    program: main.o utils.o
    	gcc main.o utils.o -o program
    main.o: main.c utils.h
    	gcc -c main.c
    utils.o: utils.c utils.h
    	gcc -c utils.c
    clean:
    	rm -f *.o program
    # Ctrl+D
    
    make
    ./program
    make clean
    
  4. 使用变量优化 Makefile:

    CC = gcc
    CFLAGS = -Wall -g
    SRCS = main.c utils.c
    OBJS = $(SRCS:.c=.o)
    TARGET = program
    
    $(TARGET): $(OBJS)
    	$(CC) $(OBJS) -o $(TARGET)
    
    %.o: %.c
    	$(CC) $(CFLAGS) -c $< -o $@
    
    clean:
    	rm -f $(OBJS) $(TARGET)
    
    .PHONY: clean
    
  5. 编译带警告的程序:

    cat > warn.c
    int main() {
        int x;
        printf("%d\n", x);    // 未初始化变量
        return 0;
    }
    # Ctrl+D
    
    gcc warn.c -o warn        # 无警告
    gcc -Wall warn.c -o warn  # 显示警告
    gcc -Werror warn.c -o warn # 警告变错误,编译失败
    
  6. 思考:为什么 make 比手动执行 gcc 命令更高效?如果修改了 utils.h 但没有修改 utils.c,make 会重新编译哪些文件?为什么?

下一页
开发工具链(下):gdb、time