环境配置 (Development Environment Setup)
章节概述
C 语言开发环境是整个学习旅程的起点。本章将手把手教你安装 GCC/Clang 编译器,配置系统 PATH 环境变量,理解编译选项(-Wall -Wextra -g -O0 -std=c11),掌握 Makefile 自动化构建和 CMake 跨平台构建系统,以及使用 GDB 进行程序调试。扎实的环境配置将为你阅读后续所有章节中的代码示例、进行动手实验提供基础保障。
核心理念:C 语言是”活在编译器里的语言”。理解编译器和工具链不仅是为了写出代码,更是为了理解代码如何变成机器指令、如何在操作系统的帮助下运行。本教程后续所有章节都假定你已经掌握了本章的工具链知识。
第一节:GCC/Clang 编译器安装
C 语言的标准化编译器是 GCC(GNU Compiler Collection),Clang(基于 LLVM)也是一个优秀的替代选择。两者都支持 C11/C17/C23 标准,且编译选项基本兼容。
1.1 Linux 安装
在 Ubuntu/Debian 系统上:
# 安装 GCC 和常用开发工具
sudo apt update
sudo apt install build-essential gdb cmake valgrind
# 验证安装
gcc --version # 应显示类似 gcc 11.4.0 的信息
g++ --version # C++ 编译器,编译混合 C/C++ 项目时需要
gdb --version # GDB 调试器
make --version # GNU Make
build-essential是一个元包,包含了gcc、g++、make、libc6-dev等基础开发工具。
在 Fedora/CentOS/RHEL 系统上:
sudo dnf groupinstall "Development Tools"
sudo dnf install cmake gdb valgrind
# 验证
gcc --version安装 Clang(可选,作为 GCC 的替代):
sudo apt install clang lldb # Debian/Ubuntu
sudo dnf install clang lldb # Fedora1.2 macOS 安装
macOS 自带 Clang(被 Xcode 命令行工具封装),但版本可能较旧:
# 安装 Xcode 命令行工具
xcode-select --install
# 或通过 Homebrew 安装更新版本
brew install gcc cmake
# 验证
gcc --version # macOS 上的 gcc 可能实际指向 clang
clang --versionmacOS 上默认的
gcc命令实际上是通过符号链接指向 Apple 的 Clang 编译器。如果你需要真正的 GCC 编译器,需要使用brew install gcc并用gcc-14命令调用。
1.3 Windows 安装(MinGW-w64 / WSL)
方案一:MinGW-w64(原生 Windows)
- 前往 winlibs.com 下载 MinGW-w64 预编译包(含 GCC + GDB + Make)
- 解压到
C:\mingw64\ - 将
C:\mingw64\bin\添加到系统 PATH 环境变量 - 打开 CMD 或 PowerShell,验证:
gcc --version
方案二:WSL2(推荐)
Windows Subsystem for Linux 让你在 Windows 上拥有完整的 Linux 环境:
# 在 PowerShell 管理员模式下
wsl --install -d Ubuntu-22.04进入 WSL 终端后,按照上述 Linux 安装步骤操作。
本教程强烈推荐使用 Linux 或 WSL2 环境。Linux 是 C 语言的”原生家园”——Linux 内核本身就用 C 语言编写。Windows 上虽然可以用 MinGW 编译,但很多底层特性(如
mmap、sbrk、clone等系统调用)在 Windows 上行为不同。
小节练习
选择题 1
build-essential包中包含哪个编译器?
A. 仅 GCC
B. 仅 Clang
C. GCC 和 G++
D. 仅 Make
点击查看答案 解析:
build-essential包含 GCC、G++、Make、libc 开发头文件等基础工具。正确答案: C
选择题 2
macOS 上
gcc命令通常实际调用的编译器是?
A. GCC
B. Clang
C. Visual C++
D. TinyCC
点击查看答案 解析: macOS 将
gcc符号链接到 Apple 版本的 Clang 编译器。使用gcc --version可以看到实际是 Clang。正确答案: B
第二节:编译选项与编译流程
2.1 常用编译选项
GCC/Clang 拥有丰富的命令行选项,以下是本教程全程使用的基础选项:
gcc -Wall -Wextra -g -O0 -std=c11 -o program source.c每个选项的含义:
| 选项 | 含义 | 推荐原因 |
|---|---|---|
-Wall | 开启所有常见警告 | 养成写干净代码的习惯 |
-Wextra | 开启额外警告 | 捕获更多潜在问题 |
-g | 生成调试信息 | GDB 调试必需 |
-O0 | 不优化(O 字母 + 数字 0) | 调试时避免变量被优化掉 |
-O2/-O3 | 优化级别 2/3 | 发布版使用 |
-std=c11 | 使用 C11 标准 | 本教程的基础标准 |
-std=c99 | 使用 C99 标准 | 兼容旧系统 |
-o program | 指定输出文件名 | 不指定则输出 a.out |
-c | 仅编译不链接 | 生成 .o 目标文件 |
-S | 生成汇编代码 | 查看 C 代码对应的汇编 |
-E | 仅预处理 | 查看宏展开后的代码 |
2.2 第一个编译命令
创建并编译你的第一个 C 程序:
# 创建源文件
echo '#include <stdio.h>
int main() {
printf("Hello, C World!\n");
return 0;
}' > hello.c
# 编译
gcc -Wall -Wextra -g -O0 -std=c11 -o hello hello.c
# 运行
./hello输出:
Hello, C World!
2.3 编译的四步骤
C 语言编译过程分为四个阶段,每个阶段对应不同的处理逻辑:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 预处理 │ ──▶ │ 编译 │ ──▶ │ 汇编 │ ──▶ │ 链接 │
│ Preprocess │ │ Compile │ │ Assemble │ │ Link │
├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤
│ .c → .i │ │ .i → .s │ │ .s → .o │ │ .o → 可执行 │
│ 展开#include │ │ C → 汇编 │ │ 汇编 → 机器码 │ │ 合并目标文件 │
│ 展开#define │ │ 语法分析 │ │ 生成重定向目标│ │ 解析符号引用 │
│ 处理条件编译 │ │ 语义分析 │ │ │ │ 包含运行时库 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
# 1. 仅预处理:查看头文件展开和宏替换后的代码
gcc -E hello.c -o hello.i
# 2. 仅编译:生成汇编代码
gcc -S hello.c -o hello.s
# 3. 仅汇编:生成目标文件
gcc -c hello.c -o hello.o
# 4. 链接:生成可执行文件
gcc hello.o -o hello汇编层面的详细解析见 。本章第二节也会在
02_第一个C程序.md中进一步展开。
小节练习
选择题 1
gcc -S hello.c生成的hello.s文件内容是什么?
A. 可执行文件
B. 预处理后的 C 代码
C. 汇编代码
D. 目标文件
点击查看答案 解析:
-S选项让编译器生成汇编代码(.s 文件),是编译(compile)阶段的输出。正确答案: C
选择题 2
gcc -O0中选项-O0的含义是?
A. 使用旧版本编译
B. 不进行代码优化
C. 开启最大优化
D. 指定输出文件名
点击查看答案 解析:
-O0(字母 O + 数字 0)表示不进行代码优化。调试时通常使用-O0 -g以保留变量和源码对应关系。正确答案: B
第三节:Makefile 自动化构建
3.1 为什么需要 Makefile
当项目有多个源文件时,手动逐个编译效率极低:
# 手工编译的痛苦
gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
gcc -c io.c -o io.o
gcc main.o utils.o io.o -o programMakefile 通过定义依赖关系,实现增量编译——只重新编译修改过的文件。
3.2 基础 Makefile 结构
# Makefile - 基本模板
CC = gcc
CFLAGS = -Wall -Wextra -g -O0 -std=c11
LDFLAGS =
TARGET = program
SRCS = main.c utils.c io.c
OBJS = $(SRCS:.c=.o)
# 默认目标(第一个目标)
all: $(TARGET)
# 链接步骤
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
# 编译规则(模式规则)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# 清理产物
clean:
rm -f $(OBJS) $(TARGET)
# 声明伪目标(不与文件名冲突)
.PHONY: all clean关键符号说明:
| 符号 | 含义 |
|---|---|
$@ | 当前规则的目标文件名 |
$^ | 所有依赖文件列表 |
$< | 第一个依赖文件 |
$(SRCS:.c=.o) | 将 .c 后缀替换为 .o |
3.3 实战 Makefile 示例
创建以下文件结构并编写 Makefile:
project/
├── Makefile
├── include/
│ └── calc.h
├── src/
│ ├── main.c
│ └── calc.c
└── build/ (构建产物目录)
# 变量定义
CC = gcc
CFLAGS = -Wall -Wextra -g -O0 -std=c11
INC_DIR = include
SRC_DIR = src
BUILD_DIR = build
TARGET = $(BUILD_DIR)/calculator
# 源文件
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/calc.c
OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
# 编译
all: $(BUILD_DIR) $(TARGET)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -I$(INC_DIR) -c -o $@ $<
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean3.4 高级 Makefile 技巧
# 自动生成依赖关系
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
@set -e; rm -f $@; \
$(CC) -I$(INC_DIR) -MM $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
# 包含生成的依赖文件
-include $(OBJS:.o=.d)Makefile 的完整讲解超出了本章范围,但掌握上述基础已足够应对本教程的所有项目。对于复杂项目,CMake 是更好的选择。
小节练习
选择题 1
Makefile 中
$@代表什么?
A. 所有依赖文件
B. 第一个依赖文件
C. 当前规则的目标文件
D. 源文件列表
点击查看答案 解析:
$@是 Makefile 的自动变量,代表当前规则的目标文件名(target)。正确答案: C
选择题 2
Makefile 实现增量编译的原理是?
A. 每次都重新编译所有文件
B. 比较源文件和目标文件的时间戳
C. 随机选择文件编译
D. 依赖 IDE 插件
点击查看答案 解析: Make 通过比较依赖文件(源文件)和目标文件的时间戳:如果依赖比目标更新,则重新编译对应规则。
正确答案: B
第四节:CMake 跨平台构建
4.1 CMake 简介
CMake 是一个跨平台的构建系统生成器——它不是直接编译代码,而是生成 Makefile、Ninja 文件、Visual Studio 项目文件等,再由对应的构建工具执行编译。
CMake 的核心优势:
- 跨平台:同一份 CMakeLists.txt 可在 Linux/macOS/Windows 上使用
- 自动依赖管理:
find_package自动查找系统库 - 模块化:可拆分为子目录和子 CMakeLists.txt
- 测试集成:内置 CTest 支持
4.2 基础 CMakeLists.txt
# 最低 CMake 版本要求
cmake_minimum_required(VERSION 3.10)
# 项目名称和语言
project(MyCProject C)
# 设置 C 标准
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 添加编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
# 收集源文件
set(SOURCES
src/main.c
src/utils.c
src/io.c
)
# 添加可执行文件
add_executable(myprogram ${SOURCES})
# 添加头文件搜索路径
target_include_directories(myprogram PRIVATE include)4.3 构建流程
# 标准 CMake 构建流程
mkdir build && cd build
cmake ..
make
# 或者使用现代 cmake 命令
cmake -B build
cmake --build build
# 运行程序
./build/myprogram构建类型切换(Debug/Release):
# Debug 模式(带调试信息,不优化)
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
# Release 模式(优化,去调试信息)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build4.4 多目录项目 CMake 示例
project/
├── CMakeLists.txt (根)
├── src/
│ ├── CMakeLists.txt
│ └── *.c
├── lib/
│ ├── CMakeLists.txt
│ └── *.c
└── include/
根 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(CDeepDive C)
set(CMAKE_C_STANDARD 11)
include_directories(include)
add_subdirectory(lib)
add_subdirectory(src)src/CMakeLists.txt:
add_executable(main main.c)
target_link_libraries(main PRIVATE mylib)lib/CMakeLists.txt:
add_library(mylib STATIC utils.c calc.c)对于简单的单文件学习项目,直接使用
gcc命令即可。多文件项目建议使用 Makefile 或 CMake。
小节练习
判断题 1
CMake 本身是一个编译器。 ( )
正确
错误
点击查看答案 解析: CMake 不是编译器,而是构建系统生成器——它生成 Makefile 或其他构建文件,再由 make/ninja/msbuild 等工具执行实际编译。编译器仍然是 GCC/Clang/MSVC 等。
答案: 错误
选择题 1
CMake 中设置 C 语言标准的命令是?
A.
set_c_standard(11)B.
set(CMAKE_C_STANDARD 11)C.
c_standard 11D.
target_use_c11()点击查看答案 解析: 使用
set(CMAKE_C_STANDARD 11)设置 C 语言标准。还需要set(CMAKE_C_STANDARD_REQUIRED ON)来强制使用该标准。正确答案: B
第五节:GDB 调试基础
5.1 为什么需要调试器
printf 调试法是最朴素的调试手段,但对于复杂 bug(段错误、内存越界、死循环),调试器是必不可少的工具。GDB(GNU Debugger)是 Linux 上最强大的 C/C++ 调试器。
5.2 编译可调试程序
# 关键:必须加 -g 生成调试信息
gcc -g -O0 -o buggy buggy.c
-g与-O2不矛盾,但优化后的代码中变量和行号对应关系可能不准确。调试阶段建议始终使用-O0。
5.3 GDB 核心命令
| 命令 | 简写 | 功能 |
|---|---|---|
break main | b main | 在函数入口设置断点 |
break 15 | b 15 | 在第 15 行设置断点 |
run | r | 启动程序 |
next | n | 单步执行(不进入函数) |
step | s | 单步执行(进入函数) |
continue | c | 继续执行到下一断点 |
print x | p x | 打印变量值 |
print *ptr | p *ptr | 解引用指针后打印 |
info locals | i lo | 显示所有局部变量 |
backtrace | bt | 显示函数调用栈 |
frame N | f N | 切换到栈帧 N |
list | l | 显示当前源码上下文 |
quit | q | 退出 GDB |
set var x=10 | — | 在调试时修改变量值 |
5.4 实战调试示例
// buggy.c - 一个包含 bug 的示例程序
#include <stdio.h>
int factorial(int n) {
if (n <= 1)
return 1;
return n * factorial(n - 1);
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
// BUG: 数组越界,i <= 5 应该是 i < 5
for (int i = 0; i <= 5; i++) {
sum += arr[i];
}
int f = factorial(-3); // BUG: 负数导致无限递归
printf("sum = %d, factorial = %d\n", sum, f);
return 0;
}# 编译
gcc -g -O0 -o buggy buggy.c
# 启动 GDB
gdb ./buggy
# GDB 会话
(gdb) break main
(gdb) run
(gdb) print arr[0]@5 # 打印 arr 的 5 个元素
(gdb) break factorial
(gdb) continue
(gdb) print n # 检查参数
(gdb) continue
(gdb) print n # 反复检查,发现 n 变成负数
(gdb) bt # 查看调用栈(会看到大量递归帧)
(gdb) quit5.5 检查段错误(Segmentation Fault)
段错误是 C 语言最常见的运行时错误之一:
// segfault.c
#include <stdio.h>
int main() {
int *p = NULL;
*p = 42; // 写入空指针 → 段错误
return 0;
}gcc -g -O0 -o segfault segfault.c
gdb ./segfault
(gdb) run
# Program received signal SIGSEGV, Segmentation fault.
# 0x... in main () at segfault.c:5
# 5 *p = 42;
(gdb) print p
# $1 = (int *) 0x0 # 一目了然:p 是空指针!段错误和内存管理是 C 语言的核心难点,本教程第二部分 02_内存模型与布局 和 03_动态内存管理 将深入展开。
5.6 Valgrind 内存错误检测
Valgrind 是检测内存泄漏和越界访问的利器:
# 使用 Valgrind 检测内存问题
valgrind --tool=memcheck --leak-check=full ./program
# 典型输出:
# ==12345== Invalid write of size 4
# ==12345== at 0x...: main (buggy.c:10) # 第10行有非法写入
# ==12345== LEAK SUMMARY:
# ==12345== definitely lost: 40 bytes in 1 blocks # 内存泄漏Valgrind 与 GDB 配合使用:
# 让 Valgrind 启动 GDB 服务器
valgrind --vgdb=yes --vgdb-error=0 ./program
# 在另一个终端:
gdb ./program
(gdb) target remote | vgdb小节练习
选择题 1
GDB 中
next和step的区别是?
A. 没有区别
B.
next会进入函数内部,step不会C.
step会进入函数内部,next不会D.
next执行多行,step执行一行点击查看答案 解析:
step(简写s)会进入被调用函数内部继续单步执行,next(简写n)将被调用函数当作整体一次执行完毕。正确答案: C
选择题 2
使用 GDB 调试程序时,编译选项必须包含哪个?
A.
-WallB.
-O2C.
-gD.
-std=c11点击查看答案 解析:
-g选项使编译器生成调试信息(源码行号、变量名、类型信息等),GDB 需要这些信息才能将机器指令映射回 C 源代码。正确答案: C
章节测试
一、判断题(正确选,错误选)
判断题 1
Clang 和 GCC 使用完全不同的编译选项体系。 ( )
正确
错误
点击查看答案 解析: Clang 在设计上兼容 GCC 的绝大多数编译选项,常用的
-Wall、-O2、-g、-std=c11等在两者上行为一致。答案: 错误
判断题 2
gcc -c hello.c直接生成可执行文件。 ( )
正确
错误
点击查看答案 解析:
-c选项表示"compile only",仅执行编译和汇编,生成目标文件hello.o,不进行链接,因此不会生成可执行文件。答案: 错误
判断题 3
Windows 上的 MinGW-w64 提供了 GCC 编译器和 GNU 工具链。 ( )
正确
错误
点击查看答案 解析: MinGW-w64(Minimalist GNU for Windows)提供了完整的 GCC 编译器、GDB 调试器、GNU Make 等工具,让 Windows 用户也能使用 GNU 工具链。
答案: 正确
判断题 4
Makefile 中的
.PHONY用于声明目标是真实存在的文件。 ( )
正确
错误
点击查看答案 解析:
.PHONY用于声明伪目标(非文件名目标),如clean、all。即使存在同名文件,make 也会执行伪目标的规则。答案: 错误
判断题 5
CMake 生成的 Makefile 和手写的 Makefile 行为完全一致。 ( )
正确
错误
点击查看答案 解析: CMake 生成的 Makefile 功能更丰富(如彩色输出、进度显示、自动重运行 cmake),结构也更复杂,与手写 Makefile 在细节上有所不同。
答案: 错误
判断题 6
GDB 只能调试由 GCC 编译的程序,不能调试 Clang 编译的程序。 ( )
正确
错误
点击查看答案 解析: GDB 可以调试任何生成了 DWARF 调试信息的程序,Clang 生成的调试信息也是 DWARF 格式,完全兼容 GDB。也可以用 LLDB 调试 Clang 编译的程序。
答案: 错误
判断题 7
gcc -E命令的输出直接可以编译为可执行文件。 ( )
正确
错误
点击查看答案 解析:
-E仅执行预处理,输出的是展开宏和包含头文件后的完整 C 源代码(.i 文件)。这个 .i 文件可以继续编译(gcc -c),但不是直接可执行。答案: 错误
判断题 8
使用 Valgrind 会使程序运行速度变慢。 ( )
正确
错误
点击查看答案 解析: Valgrind 在虚拟机上解释执行程序指令并跟踪所有内存操作,通常会比正常运行慢 10-20 倍。Valgrind 仅用于开发和调试,不应用于生产环境。
答案: 正确
判断题 9
set(CMAKE_BUILD_TYPE Debug)自动包含了-g编译选项。 ( )
正确
错误
点击查看答案 解析: CMake 的 Debug 构建类型默认添加
-g调试信息,Release 默认添加-O3优化并去掉调试信息并定义NDEBUG。答案: 正确
判断题 10
在使用
-O2优化的情况下,GDB 单步调试时程序执行顺序可能与源代码行号不一致。 ( )
正确
错误
点击查看答案 解析: 编译器优化可能重排指令、内联函数、消除变量,导致单步执行时出现"行号跳来跳去"或"变量不可见"的情况。调试阶段建议使用
-O0。答案: 正确
二、选择题(单项选择题)
选择题 1
C 语言编译过程中,
.o文件在哪个阶段生成?
A. 预处理
B. 编译
C. 汇编
D. 链接
点击查看答案 解析: 汇编阶段将汇编代码(.s)转换为机器码,生成重定位目标文件(.o)。预处理生成 .i,编译生成 .s,链接将 .o 合并生成可执行文件。
正确答案: C
选择题 2
以下哪个不是 GCC 的有效编译选项?
A.
-WallB.
-WextraC.
-WtypeD.
-Wpedantic点击查看答案 解析:
-Wtype不是 GCC 的有效选项。有效选项包括-Wall(常见警告)、-Wextra(额外警告)、-Wpedantic(严格标准检查)等。正确答案: C
选择题 3
Makefile 中
$(OBJS:.c=.o)的含义是?
A. 将 OBJS 中所有的
.c替换为.oB. 将 OBJS 中的
.c文件编译为.o文件C. 输出 OBJS 列表
D. 语法错误
点击查看答案 解析:
$(VAR:old=new)是 Makefile 的替换引用语法,将变量中每个匹配old后缀的字符串替换为new后缀。正确答案: A
选择题 4
CMake 中
target_include_directories的PRIVATE关键字表示?
A. 头文件目录对依赖者可见
B. 头文件目录仅对该目标自身可见
C. 头文件目录对所有人可见
D. 编译器默认搜索
点击查看答案 解析:
PRIVATE表示包含目录仅对该目标自身有效,依赖该目标的其他目标不会自动获得这些头文件路径。PUBLIC会传递,INTERFACE仅对依赖者传递。正确答案: B
选择题 5
GDB 中查看函数调用栈的命令是?
A.
stackB.
callstackC.
backtraceD.
trace点击查看答案 解析:
backtrace(简写bt)显示当前函数调用栈,包括每帧的函数名、参数、源码位置。正确答案: C
选择题 6
以下关于
gcc -S hello.c的说法正确的是?
A. 生成可执行文件 hello.s
B. 生成汇编代码文件 hello.s
C. 仅编译不汇编
D. 生成目标文件 hello.s
点击查看答案 解析:
-S选项执行预处理和编译两个阶段,将 C 代码编译为汇编代码并输出到 .s 文件。不执行汇编和链接。正确答案: B
选择题 7
Valgrind 主要用于检测什么?
A. 语法错误
B. 编译警告
C. 内存错误和泄漏
D. 代码风格问题
点击查看答案 解析: Valgrind 是一套运行时分析工具,其中最常用的 Memcheck 工具用于检测内存泄漏、非法访问、未初始化内存使用等内存错误。
正确答案: C
选择题 8
在 WSL2 中开发 C 程序,程序实际上运行在什么内核上?
A. Windows NT 内核
B. Linux 内核
C. MinGW 内核
D. 混合内核
点击查看答案 解析: WSL2 运行在 Hyper-V 虚拟机中的完整 Linux 内核上,因此 C 程序使用 Linux 系统调用,与原生 Linux 行为一致。
正确答案: B
选择题 9
gdb ./program后执行run arg1 arg2,这些参数会传给谁?
A. 传给 GDB 本身
B. 传给被调试的 program 的 main 函数
C. 丢失,不会被传递
D. 传给操作系统内核
点击查看答案 解析: GDB 的
run命令接受参数,这些参数将作为命令行参数传递给被调试程序的main(argc, argv)。正确答案: B
选择题 10
以下哪个 Makefile 变量存储的是 C 编译器的名称?
A.
$(CC)B.
$(CXX)C.
$(COMPILER)D.
$(GCC)点击查看答案 解析:
CC是 Makefile 中约定俗成的 C 编译器变量名(默认为cc)。CXX对应 C++ 编译器。这些是惯例变量名而非强制标准。正确答案: A
️ 动手练习题
练习题 1:编译流程全体验
难度:
编写一个包含
#include <stdio.h>和#define PI 3.14的简单 C 程序,手动执行编译四步骤:
gcc -E生成预处理文件,观察头文件展开后的代码量gcc -S生成汇编文件,尝试辨认main函数对应的汇编指令gcc -c生成目标文件,用objdump -d查看反汇编gcc链接生成可执行文件每一步完成后用
wc -l或ls -la对比文件大小。
练习题 2:编写完整 Makefile
难度:
创建一个包含 3 个源文件的项目(main.c, math.c, string.c),编写 Makefile:
- 支持增量编译(只编译修改过的文件)
- 使用
CFLAGS = -Wall -Wextra -g -std=c11- 包含
clean和all伪目标- 构建产物放在
build/目录中- 能够处理头文件依赖(当 .h 文件修改时,引用它的 .c 文件也重新编译)
练习题 3:GDB 调试练习
难度:
编写一个包含递归函数和数组操作的程序,故意引入以下 bug:
- 递归函数在某个条件下无限递归
- 数组写入越界
使用 GDB 定位 bug:
- 用
break和run启动调试- 用
backtrace观察递归栈深度- 用
- 用
watch监视某个变量何时被意外修改
练习题 4:CMake 项目搭建
难度:
为一个包含
src/lib、src/app、include/三层结构的 C 项目编写 CMakeLists.txt:
lib子目录编译为静态库app子目录编译为可执行文件,并链接静态库- 使用
-Wall -Wextra -std=c11编译选项- 设置 Debug 和 Release 两种构建类型
练习题 5:Valgrind 内存检查
难度:
力扣练习:力扣区间覆盖题
用 C 语言实现后,使用 Valgrind 检查是否存在内存泄漏或非法访问。观察 Valgrind 的输出报告格式,确认”All heap blocks were freed — no leaks are possible”。