C语言作为一门底层且灵活的编程语言,存在许多容易混淆或出错的知识点。以下是一些常见的易错点及其解析:
1. 指针与数组
-
混淆点:指针和数组的相似性与差异。
- 数组名不是指针:数组名是首元素地址的常量(不可修改),但
sizeof(arr)
返回数组总大小,而指针返回指针的大小。 - 声明区别:
int *p
是指针,int a[10]
是数组。尽管p = a
合法,但a = p
非法(数组名不可修改)。
int a[5]; int *p = a; // 合法 a = p; // 非法!
- 数组名不是指针:数组名是首元素地址的常量(不可修改),但
-
函数参数传递:数组作为函数参数时退化为指针。
void func(int arr[]); // 实际等价于 void func(int *arr)
2. 内存管理
-
内存泄漏:忘记释放
malloc
/calloc
分配的内存。int *p = (int*)malloc(10 * sizeof(int)); // 必须最后调用 free(p);
-
野指针:释放内存后未置空指针。
free(p); p = NULL; // 避免后续误用
-
重复释放:对同一指针多次调用
free
会导致未定义行为。
3. 指针与函数
- 函数指针与指针函数:
- 指针函数:返回指针的函数,如
int* func()
; - 函数指针:指向函数的指针,如
int (*func_ptr)(int, int)
;
int add(int a, int b) { return a + b; } int (*func_ptr)(int, int) = add; // 函数指针
- 指针函数:返回指针的函数,如
4. 预处理与宏
-
宏的副作用:宏参数可能被多次求值。
#define MAX(a, b) ((a) > (b) ? (a) : (b)) int x = 1, y = 2; MAX(x++, y++); // x++ 可能被求值两次!
-
条件编译陷阱:
#ifdef DEBUG // 可能因为未定义 DEBUG 被忽略 #endif
5. 结构体与联合体
-
内存对齐:结构体成员因对齐规则导致实际大小与预期不同。
struct S { char c; // 1字节 int i; // 4字节(可能在前填充3字节) }; // sizeof(struct S) 可能是 8 而非 5
-
联合体共享内存:所有成员共享同一块内存,修改一个会影响其他成员。
6. 作用域与链接性
-
static
关键字:- 函数内:变量生命周期延长到程序结束,但作用域仍为局部。
- 全局变量/函数:限制为当前文件可见(避免命名冲突)。
-
extern
关键字:声明变量/函数在其他文件中定义。
7. 类型转换与溢出
-
隐式类型转换:混合类型运算可能导致意外结果。
int a = 5; float b = a / 2; // 结果为 2.0(整数除法) float c = (float)a / 2; // 正确:2.5
-
有符号与无符号比较:
int a = -1; unsigned int b = 100; if (a < b) { /* 结果为 false!因为 a 被转为无符号数 */ }
8. 标准库函数误用
-
字符串函数:
strcpy
不检查缓冲区大小,可能导致溢出,推荐strncpy
。strlen
返回长度不包含\0
,分配内存时需+1
。
-
scanf
的陷阱:char str[10]; scanf("%s", str); // 输入超长会导致溢出,建议用 fgets
9. 运算符优先级
-
常见错误:
if (x & 1 == 0) { ... } // 实际等价于 x & (1 == 0) // 应写为 if ((x & 1) == 0)
-
自增/自减运算符:
int i = 0; int j = i++ + i++; // 未定义行为(同一变量多次修改)
10. 未定义行为(UB)
- 常见UB场景:
- 数组越界访问:
int arr[3]; arr[5] = 10;
- 使用未初始化变量:
int x; printf("%d", x);
- 修改字符串字面量:
char *s = "hello"; s[0] = 'H';
(应使用字符数组)
- 数组越界访问:
11. 浮点数比较
- 精度问题:直接比较浮点数可能导致错误。
float a = 0.1 + 0.2; if (a == 0.3) { /* 可能不成立! */ } // 应使用误差范围:fabs(a - 0.3) < 1e-6
总结
C语言的灵活性是一把双刃剑,需要严格遵循语法规则和内存管理原则。建议通过以下方法避免错误:
- 使用静态分析工具(如
clang-tidy
)。 - 启用编译器警告(如
gcc -Wall -Wextra
)。 - 编写单元测试验证边界条件。
- 阅读经典书籍(如《C陷阱与缺陷》)。