十大常见陷阱
1. 数组越界
int arr[5] = {1, 2, 3, 4, 5}; arr[5] = 10; // 越界!不报错,可能改坏别的数据C 语言不检查数组越界。后果:改坏相邻变量、段错误、"看起来正常"但埋隐患。
防御:循环条件用< 长度,不用<=。
2. 忘记 \0
char name[5]; strcpy(name, "hello"); // "hello" 需要 6 字节(含 \0),溢出!
防御:声明字符数组时多留 1 字节给\0。用strncpy限制长度。
3. 野指针(悬垂指针)
int *p = malloc(sizeof(int)); free(p); *p = 10; // 未定义行为!p 已经是野指针
防御:free(p)后立刻p = NULL。
4. 内存泄漏
void leak() { int *p = malloc(1000); return; // 忘记 free(p),内存泄漏 }malloc和free必须配对。长期运行的程序泄漏会慢慢吃光内存。
检测工具:Valgrind(Linux)、dr.memory(Windows)。
5. = 和 == 搞混
if (a = 5) // 永远为真!赋值 if (a == 5) // 比较
C 语言最常见的逻辑 bug 来源之一。
6. switch 没 break
switch (x) { case 1: printf("one"); // 没 break,穿透 case 2: printf("two"); // 也会执行 }防御:每个 case 末尾加break。
7. 整数除法截断
float result = 1 / 3; // result = 0.0(整数除法) float result = 1.0 / 3; // result = 0.333...(浮点除法)
防御:要小数结果,至少一个操作数用浮点类型。
8. scanf 忘记 &
int x; scanf("%d", x); // 错!应该是 &x scanf("%d", &x); // 对字符串不需要&(数组名本身就是地址):
char name[50]; scanf("%s", name); // 正确9. sizeof 误解
int arr[5]; printf("%zu", sizeof(arr)); // 20(整个数组) void func(int arr[]) { printf("%zu", sizeof(arr)); // 8(指针,不是数组!) }数组传函数后退化为指针,sizeof失效。
10. 未初始化变量
int x; printf("%d\n", x); // 垃圾值,未定义行为防御:声明时赋初值:int x = 0;。
调试技巧
printf 调试法
最简单直接,在关键位置打印变量值:
printf("DEBUG: x = %d, p = %p\n", x, (void*)p);GDB 调试器
gcc -g program.c -o program # 编译时加 -g 保留调试信息 gdb ./program # 启动 GDB (gdb) run # 运行 (gdb) backtrace # 崩溃后查看调用栈 (gdb) print x # 查看变量值 (gdb) break main # 在 main 设断点 (gdb) next # 单步执行
Valgrind 内存检测
valgrind ./program
检测:内存泄漏、越界访问、使用未初始化内存、double free。
常见错误信息对照
| 错误信息 | 原因 |
|---|---|
| Segmentation fault | 解引用无效指针、数组越界 |
| Bus error | 内存对齐问题 |
| Double free | 对同一指针 free 两次 |
| Use after free | free 后继续使用指针 |
| Stack overflow | 递归无基线条件、栈上数组太大 |
防御性编程习惯
声明变量时赋初值— 避免垃圾值
malloc 后立刻检查 NULL— 避免空指针
free 后立刻置 NULL— 避免野指针
字符串操作用安全版本—
strncpy、snprintf循环条件用
<不用<=— 避免 off-by-one不确定优先级就加括号— 避免运算顺序错误
函数参数传指针时标注 const— 防止意外修改
总结
C 的陷阱大多源于"不检查"——越界、类型、空指针都不会报错
调试三件套:printf、GDB、Valgrind
防御性编程比事后调试更重要——写代码时就避免陷阱
遇到 Segmentation fault 先查指针和数组越界