CentOS Stream 9 中 Linux C 编程 ---语法详解与实战案例
一、概述
在 Linux 系统中进行 C 语言开发,主要依赖三大核心工具:
| 工具 | 功能 |
|---|---|
| GCC | GNU 编译器集合,用于编译 C/C++ 代码 |
| GDB | GNU 调试器,用于调试程序运行时错误 |
| Make / Makefile | 自动化编译工具,管理多文件项目依赖 |
| Autotools | 项目自动化构建系统(configure + Makefile) |
✅ 开发环境准备(CentOS Stream 9):
bash
# 安装开发工具包
sudo dnf groupinstall "Development Tools" -y
# 安装调试工具
sudo dnf install gdb -y
# 安装 Autotools
sudo dnf install autoconf automake libtool -y
# 验证安装
gcc --version
gdb --version
make --version
二、GCC 编译
2.1 GCC 工具链
GCC(GNU Compiler Collection)包含多个工具:
| 工具 | 作用 |
|---|---|
gcc |
C 语言编译器前端 |
cpp |
C 预处理器 |
as |
汇编器 |
ld |
链接器 |
ar |
静态库打包工具 |
nm、objdump、readelf |
目标文件分析工具 |
✅ 编译流程四阶段:
源代码(.c)
→ 预处理(.i)
→ 编译(.s)
→ 汇编(.o)
→ 链接(可执行文件)
2.2 gcc 命令基本用法
基本语法:
bash
gcc [选项] [输入文件] -o [输出文件]
常用选项:
| 选项 | 说明 |
|---|---|
-E |
只进行预处理 |
-S |
只进行编译到汇编 |
-c |
只编译到目标文件(.o) |
-o |
指定输出文件名 |
-g |
生成调试信息(供 GDB 使用) |
-Wall |
启用所有警告 |
-O0/-O1/-O2/-O3 |
优化级别(0=无优化,3=最高优化) |
-I<目录> |
指定头文件搜索路径 |
-L<目录> |
指定库文件搜索路径 |
-l<库名> |
链接指定库(如 -lm 链接 math 库) |
2.3 gcc 使用实例
✅ 案例1:编译单文件程序
c
// hello.c
#include <stdio.h>
int main() {
printf("Hello, Linux C Programming!\n");
return 0;
}
bash
# 编译并运行
gcc hello.c -o hello
./hello
# 输出:Hello, Linux C Programming!
✅ 案例2:分步编译(预处理 → 编译 → 汇编 → 链接)
bash
# 1. 预处理(展开宏、包含头文件)
gcc -E hello.c -o hello.i
# 2. 编译为汇编代码
gcc -S hello.i -o hello.s
# 3. 汇编为目标文件
gcc -c hello.s -o hello.o
# 4. 链接生成可执行文件
gcc hello.o -o hello
# 或者一步到位
gcc hello.c -o hello
✅ 案例3:启用调试和警告
bash
gcc -g -Wall -O0 hello.c -o hello_debug
# -g:生成调试信息
# -Wall:显示所有警告
# -O0:不优化,便于调试
✅ 案例4:链接数学库
c
// math_test.c
#include <stdio.h>
#include <math.h>
int main() {
double x = 2.0;
double result = sqrt(x);
printf("sqrt(%.1f) = %.6f\n", x, result);
return 0;
}
bash
# 必须链接数学库 -lm
gcc math_test.c -o math_test -lm
./math_test
# 输出:sqrt(2.0) = 1.414214
三、综合案例:使用 GCC 编译包含多个源文件的项目
3.1 案例概述
项目结构:
project/
├── main.c // 主函数
├── calc.h // 函数声明
├── calc.c // 函数实现
└── Makefile // 后续章节使用
功能:实现加减乘除计算器
3.2 案例详解
✅ 文件1:
calc.h(头文件)
c
// calc.h - 函数声明
#ifndef CALC_H
#define CALC_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法(注意除零检查)
double divide(int a, int b);
#endif
✅ 文件2:
calc.c(函数实现)
c
// calc.c - 函数定义
#include "calc.h"
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
double divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "Error: Division by zero!\n");
return 0.0;
}
return (double)a / (double)b;
}
✅ 文件3:
main.c(主程序)
c
// main.c - 主函数
#include <stdio.h>
#include "calc.h"
int main() {
int x = 10, y = 5;
printf("x = %d, y = %d\n", x, y);
printf("%d + %d = %d\n", x, y, add(x, y));
printf("%d - %d = %d\n", x, y, subtract(x, y));
printf("%d * %d = %d\n", x, y, multiply(x, y));
printf("%d / %d = %.2f\n", x, y, divide(x, y));
// 测试除零
printf("10 / 0 = %.2f\n", divide(10, 0));
return 0;
}
✅ 手动编译命令:
bash
# 方法1:一步编译所有源文件
gcc main.c calc.c -o calculator -g -Wall
# 方法2:分步编译(推荐用于大型项目)
gcc -c main.c -o main.o # 编译 main.c
gcc -c calc.c -o calc.o # 编译 calc.c
gcc main.o calc.o -o calculator # 链接
# 运行
./calculator
✅ 输出:
x = 10, y = 5
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2.00
Error: Division by zero!
10 / 0 = 0.00
四、GDB 调试
4.1 GDB 基本命令
编译时必须加
-g选项:
bash
gcc -g -Wall main.c calc.c -o calculator
常用 GDB 命令:
| 命令 | 说明 |
|---|---|
gdb ./program |
启动 GDB |
run 或 r |
运行程序 |
break 或 b |
设置断点(b main, b 10, b calc.c:5) |
continue 或 c |
继续执行 |
next 或 n |
单步执行(不进入函数) |
step 或 s |
单步执行(进入函数) |
print 或 p |
打印变量值(p x, p result) |
list 或 l |
显示源代码 |
backtrace 或 bt |
显示调用栈 |
quit 或 q |
退出 GDB |
4.2 综合案例:使用 GDB 调试 C 语言项目
4.2.1 案例概述
修改
calc.c引入一个 bug,使用 GDB 定位并修复。
4.2.2 案例详解
✅ 修改
calc.c(故意制造 bug):
c
// calc.c - 有 bug 的版本
#include "calc.h"
#include <stdio.h>
int add(int a, int b) {
return a + b; // 正常
}
int subtract(int a, int b) {
return a - b; // 正常
}
int multiply(int a, int b) {
return a * b + 1; // BUG: 多加了1!
}
double divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "Error: Division by zero!\n");
return 0.0;
}
return (double)a / (double)b;
}
✅ 重新编译:
bash
gcc -g -Wall main.c calc.c -o calculator
✅ 启动 GDB:
bash
gdb ./calculator
✅ GDB 调试过程:
gdb
(gdb) break multiply # 在 multiply 函数设断点
Breakpoint 1 at 0x40114e: file calc.c, line 12.
(gdb) run # 运行程序
Starting program: /home/user/project/calculator
x = 10, y = 5
10 + 5 = 15
10 - 5 = 5
Breakpoint 1, multiply (a=10, b=5) at calc.c:12
12 return a * b + 1; # 停在这里
(gdb) print a # 查看变量 a
$1 = 10
(gdb) print b # 查看变量 b
$2 = 5
(gdb) next # 执行下一行
13 }
(gdb) print a * b + 1 # 手动计算
$3 = 51 # 应该是50,多加了1!
(gdb) quit # 退出
✅ 修复 bug:
c
// 修复:去掉 +1
int multiply(int a, int b) {
return a * b; // 修复后
}
✅ 重新编译并测试:
bash
gcc -g -Wall main.c calc.c -o calculator
./calculator
# 输出:10 * 5 = 50 (正确!)
五、Make 编译
5.1 make 和 Makefile 概述
Makefile 是一个文本文件,定义了:
- 目标(target)
- 依赖(dependencies)
- 命令(commands)
make 工具根据 Makefile 自动判断哪些文件需要重新编译。
5.2 Makefile 语法基础
基本语法:
makefile
target: dependencies
<TAB>command
✅ 示例:
makefile
# 最简单的 Makefile
hello: hello.c
gcc hello.c -o hello
特殊变量:
| 变量 | 说明 |
|---|---|
$@ |
目标文件名 |
$< |
第一个依赖文件 |
$^ |
所有依赖文件 |
伪目标(.PHONY):
makefile
.PHONY: clean
clean:
rm -f *.o calculator
5.3 Makefile 实例
✅ 基础版 Makefile(对应前面的计算器项目)
makefile
# Makefile for calculator project
# 编译器
CC = gcc
# 编译选项
CFLAGS = -g -Wall
# 目标文件
TARGET = calculator
# 源文件
SRCS = main.c calc.c
# 目标文件
OBJS = $(SRCS:.c=.o) # main.o calc.o
# 默认目标
all: $(TARGET)
# 链接目标文件
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# 编译 .c 到 .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
# 重新编译
.PHONY: rebuild
rebuild: clean all
✅ 使用:
bash
# 编译
make
# 清理
make clean
# 重新编译
make rebuild
# 查看帮助(可选添加)
.PHONY: help
help:
@echo "Available targets:"
@echo " make - 编译项目"
@echo " make clean - 清理目标文件"
@echo " make rebuild - 重新编译"
5.4 Make 编译的基本步骤
- 编写 Makefile:定义目标、依赖、命令
- 运行 make:自动检查依赖,只编译修改过的文件
- 增量编译:节省时间,提高效率
- 清理项目 :
make clean删除中间文件
六、综合案例:使用 Makefile 管理 C 语言项目
6.1 案例概述
项目结构升级:
advanced_project/
├── src/
│ ├── main.c
│ ├── calc.c
│ └── utils.c
├── include/
│ ├── calc.h
│ └── utils.h
├── lib/
├── bin/
└── Makefile
新增功能:字符串工具函数
6.2 案例详解1(基础版)
✅ 创建目录结构:
bash
mkdir -p advanced_project/{src,include,lib,bin}
cd advanced_project
✅ 文件1:
include/utils.h
c
// utils.h
#ifndef UTILS_H
#define UTILS_H
// 字符串反转
void reverse_string(char *str);
// 字符串长度
int string_length(const char *str);
#endif
✅ 文件2:
src/utils.c
c
// utils.c
#include "utils.h"
int string_length(const char *str) {
int len = 0;
while (str[len] != '\0') {
len++;
}
return len;
}
void reverse_string(char *str) {
int len = string_length(str);
int i, j;
char temp;
for (i = 0, j = len - 1; i < j; i++, j--) {
temp = str[i];
str[i] = str[j];
str[j] = temp;
}
}
✅ 修改
src/main.c
c
// main.c
#include <stdio.h>
#include "calc.h"
#include "utils.h"
int main() {
int x = 10, y = 5;
char test_str[] = "Hello World";
printf("=== Calculator Test ===\n");
printf("%d + %d = %d\n", x, y, add(x, y));
printf("%d * %d = %d\n", x, y, multiply(x, y));
printf("\n=== String Utils Test ===\n");
printf("Original: %s\n", test_str);
reverse_string(test_str);
printf("Reversed: %s\n", test_str);
return 0;
}
✅ 基础版 Makefile:
makefile
# Makefile - 基础版
CC = gcc
CFLAGS = -g -Wall -I./include
TARGET = bin/calculator
SRCDIR = src
INCDIR = include
BINDIR = bin
SRCS = $(wildcard $(SRCDIR)/*.c)
OBJS = $(SRCS:$(SRCDIR)/%.c=$(BINDIR)/%.o)
$(BINDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(BINDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJS)
@mkdir -p $(dir $@)
$(CC) $(OBJS) -o $@
.PHONY: clean
clean:
rm -rf $(BINDIR)
.PHONY: run
run: $(TARGET)
./$(TARGET)
.PHONY: all
all: $(TARGET)
.PHONY: help
help:
@echo "Targets:"
@echo " make - 编译"
@echo " make run - 运行"
@echo " make clean - 清理"
✅ 编译运行:
bash
make
make run
输出:
=== Calculator Test ===
10 + 5 = 15
10 * 5 = 50
=== String Utils Test ===
Original: Hello World
Reversed: dlroW olleH
6.3 案例详解2(进阶版)
✅ 进阶版 Makefile(支持调试/发布模式、自动依赖)
makefile
# Makefile - 进阶版
# ============ 配置 ============
CC = gcc
BINDIR = bin
SRCDIR = src
INCDIR = include
LIBDIR = lib
# 调试模式(默认)或发布模式
DEBUG ?= 1
ifeq ($(DEBUG), 1)
CFLAGS = -g -Wall -O0 -DDEBUG
TARGET = $(BINDIR)/calculator_debug
else
CFLAGS = -O2 -DNDEBUG
TARGET = $(BINDIR)/calculator_release
endif
CFLAGS += -I$(INCDIR)
# 自动发现源文件
SRCS = $(wildcard $(SRCDIR)/*.c)
OBJS = $(SRCS:$(SRCDIR)/%.c=$(BINDIR)/%.o)
DEPS = $(OBJS:.o=.d)
# 自动生成依赖
$(BINDIR)/%.d: $(SRCDIR)/%.c
@mkdir -p $(dir $@)
@$(CC) -MM -MT "$(BINDIR)/$*.o" -MF $@ $(CFLAGS) $<
# 包含依赖文件(如果存在)
-include $(DEPS)
# 编译规则
$(BINDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
# 链接
$(TARGET): $(OBJS)
@mkdir -p $(dir $@)
$(CC) $(OBJS) -o $@
# 清理
.PHONY: clean
clean:
rm -rf $(BINDIR)/*
# 重新编译
.PHONY: rebuild
rebuild: clean $(TARGET)
# 运行
.PHONY: run
run: $(TARGET)
./$(TARGET)
# 调试(使用 GDB)
.PHONY: debug
debug: $(TARGET)
gdb ./$(TARGET)
# 发布版本
.PHONY: release
release:
$(MAKE) DEBUG=0
# 默认目标
.PHONY: all
all: $(TARGET)
# 帮助
.PHONY: help
help:
@echo "Advanced Makefile Usage:"
@echo " make - 编译调试版本"
@echo " make DEBUG=0 - 编译发布版本"
@echo " make run - 运行程序"
@echo " make debug - 使用 GDB 调试"
@echo " make release - 编译发布版本"
@echo " make clean - 清理"
@echo " make rebuild - 重新编译"
✅ 使用示例:
bash
# 调试版本
make
make run
# 发布版本
make release
./bin/calculator_release
# 调试程序
make debug
# 清理
make clean
七、Makefile 自动生成技术
7.1 Autotools 简介
Autotools 是一套自动化构建工具,包含:
| 工具 | 作用 |
|---|---|
| autoconf | 生成 configure 脚本 |
| automake | 生成 Makefile.in |
| libtool | 管理库文件 |
工作流程:
Makefile.am + configure.ac
→ automake → Makefile.in
→ autoconf → configure
→ ./configure → Makefile
→ make → 可执行文件
八、综合案例:使用 Autotools 管理 C 语言项目
8.1 案例概述
项目结构:
autotools_project/
├── configure.ac # autoconf 配置
├── Makefile.am # automake 配置
├── src/
│ ├── Makefile.am # 子目录 Makefile.am
│ ├── main.c
│ ├── calc.c
│ └── utils.c
└── include/
├── calc.h
└── utils.h
8.2 案例详解
✅ 步骤1:创建
configure.ac
bash
# 在项目根目录创建 configure.ac
touch configure.ac
autoconf
# configure.ac
AC_INIT([calculator], [1.0], [your-email@example.com])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_PROG_CC
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT
✅ 步骤2:创建根目录
Makefile.am
makefile
# Makefile.am
SUBDIRS = src
✅ 步骤3:创建
src/Makefile.am
makefile
# src/Makefile.am
bin_PROGRAMS = calculator
calculator_SOURCES = main.c calc.c utils.c
calculator_CFLAGS = -I$(top_srcdir)/include
✅ 步骤4:运行 Autotools 生成构建系统
bash
# 1. 生成 aclocal.m4
aclocal
# 2. 生成 configure 脚本
autoconf
# 3. 生成 Makefile.in
automake --add-missing
# 4. 运行 configure 生成 Makefile
./configure
# 5. 编译
make
# 6. 安装(可选)
sudo make install
# 7. 清理
make clean
✅ 完整构建脚本
build.sh:
bash
#!/bin/bash
# build.sh - 一键构建脚本
set -e # 遇错停止
echo "🔄 正在生成构建系统..."
# 生成 aclocal.m4
echo "1/5: 运行 aclocal..."
aclocal
# 生成 configure
echo "2/5: 运行 autoconf..."
autoconf
# 生成 Makefile.in
echo "3/5: 运行 automake..."
automake --add-missing
# 配置项目
echo "4/5: 运行 configure..."
./configure
# 编译
echo "5/5: 运行 make..."
make
echo "✅ 构建完成!可执行文件:src/calculator"
echo "💡 运行:./src/calculator"
✅ 使用:
bash
chmod +x build.sh
./build.sh
./src/calculator
✅ 支持标准操作:
bash
./configure --prefix=/usr/local # 指定安装路径
make
make check # 运行测试(需配置)
sudo make install # 安装
make uninstall # 卸载(需配置)
✅ Linux C 编程最佳实践
- 始终使用
-Wall -Wextra:开启所有警告 - 调试版本加
-g:便于 GDB 调试 - 使用 Makefile:管理多文件项目
- 分离头文件和源文件 :
include/和src/ - 使用 Autotools:便于跨平台分发
- 版本控制:使用 Git 管理代码
- 代码格式化 :使用
indent或clang-format - 静态分析 :使用
cppcheck或splint
📚 附录:常用命令速查表
| 功能 | 命令 |
|---|---|
| 编译单文件 | gcc -g -Wall file.c -o program |
| 调试程序 | gdb ./program |
| 设置断点 | break function_name 或 break line_number |
| 运行 Makefile | make |
| 清理 | make clean |
| 生成 Autotools | aclocal && autoconf && automake --add-missing |
| 配置项目 | ./configure |
| 安装 | sudo make install |
这份文档覆盖了 CentOS Stream 9 上 Linux C 编程的全部核心知识点 + 语法细节 + 实用案例 + 综合项目,所有代码均含详细注释,可直接用于教学、自学或生产环境参考。