C语言的未定义行为(UB)是编译器可自由处理的代码陷阱,可能导致程序崩溃、逻辑错误或不可预测结果。以下从表达式计算、内存操作和指针使用三个维度系统盘点高频UB场景,结合标准规范与避坑方案,助你构建健壮代码。
一、表达式计算中的未定义行为
核心问题:序列点缺失导致副作用冲突。
1. 同一序列点内多次修改变量
典型UB:
int i = 3;
i = i++; // UB:赋值与自增副作用无序列点分隔
危害:
- 结果依赖编译器实现(GCC输出3,MSVC输出4)
- 极端优化可能消除表达式(如直接返回初始值)
避坑方案:
int i = 3;
int temp = i++; // 序列点确保副作用完成
i = temp; // 显式赋值
2. 子表达式求值顺序未定义
典型UB:
int result = f() + g(); // f()和g()调用顺序未定义
危害:
- 若函数含副作用(如修改全局变量),结果不可控
- 示例中
f()和g()均修改全局变量$x$,$result$可能为$0+1=1$或$1+0=1$
避坑方案:
int a = f(); // 显式分隔求值
int b = g();
int result = a + b;
3. 自增/自减副作用生效时机未定义
典型UB:
int i = 5;
int x = arr[i++]; // 取值与自增顺序未定义
危害:
- 可能先取
arr[5](越界UB)后自增,或先自增后取arr[5](仍越界) - 数组下标范围$[0, N-1]$,访问$arr[N]$必越界
避坑方案:
int i = 4; // 确保下标合法
int x = arr[i]; // 显式取值
i++; // 显式自增
二、内存操作中的未定义行为
核心问题:非法内存分配/释放。
1. malloc(0)返回值未定义
典型UB:
void* p = malloc(0); // 可能返回NULL或非NULL指针
危害:
- 返回非NULL时需
free(p),否则内存泄漏 - 返回NULL时若误判“分配成功”,导致后续非法访问
避坑方案:
void* p = malloc(size > 0 ? size : 1); // 避免分配0字节
if (p) { /* 使用并free */ }
2. free非动态内存或重复释放
典型UB:
int x;
free(&x); // UB:释放栈内存
free(p); free(p); // UB:重复释放
危害:
- 释放栈内存破坏栈帧结构,触发段错误
- 重复释放破坏堆管理器数据结构
避坑方案:
free(p);
p = NULL; // 置空指针,后续free(NULL)安全
3. 二进制文件文本模式读写
典型UB:
FILE* fp = fopen("data.bin", "w"); // 文本模式写二进制
fwrite(&data, sizeof(data), 1, fp);
危害:
- Windows下$\text{\textbackslash n} \rightarrow \text{\textbackslash r\textbackslash n}$转换破坏数据
- 移植到Linux虽无问题,但反向移植出错
避坑方案:
FILE* fp = fopen("data.bin", "wb"); // 明确二进制模式
三、指针使用中的未定义行为
核心问题:非法指针解引用。
1. 空指针解引用
典型UB:
int* p = NULL;
*p = 10; // UB:解引用空指针
危害:
- 多数系统触发段错误($SIGSEGV$)
- 嵌入式系统可能静默覆盖关键内存
避坑方案:
if (p != NULL) *p = 10; // 显式检查
2. 野指针访问
典型UB:
int* p; // 未初始化野指针
*p = 10; // UB:访问随机地址
int* q = malloc(4);
free(q);
*q = 20; // UB:访问已释放内存
危害:
- 野指针覆盖随机内存导致数据污染
- 悬空指针修改已释放内存破坏其他变量
避坑方案:
int* p = NULL; // 初始化置空
if (need_mem) p = malloc(4);
free(p); p = NULL; // 释放后置空
总结:构建“无UB”代码的关键原则
- 表达式拆分:用分号显式分隔含副作用的操作
- 内存操作规范:
- 避免
malloc(0),检查分配结果 free后指针置空,仅释放动态内存
- 避免
- 指针安全:
- 解引用前检查
NULL - 初始化指针并及时置空悬垂指针
- 解引用前检查
- 工具辅助:
- 使用
Valgrind检测内存错误 - 编译器警告选项(如
gcc -Wall -Werror)
- 使用
通过严格遵循序列点规则、内存管理契约和指针校验,可显著降低UB风险,提升代码健壮性与可移植性。
920

被折叠的 条评论
为什么被折叠?



