一、printf
的%n
格式说明符
作用
-
写入输出字符数:将当前已输出的字符数量写入参数指定的内存地址
c
int count; printf("Hello%n", &count); // 输出"Hello"后count=5
风险
-
格式化字符串攻击:若用户控制格式字符串,可向任意地址写入数据
c
// 危险代码! printf(user_input); // 用户输入"%100x%n"可覆盖返回地址
防御措施
-
禁用
%n
:使用printf_s
等安全版本 -
隔离用户输入:
printf("%s", user_input)
二、信号处理函数中不可重入函数
根本原因
-
异步执行冲突:信号处理函数中断主程序时:
-
修改全局状态/静态缓冲区 → 主程序恢复后状态错乱
-
操作共享资源(文件/内存池)→ 数据损坏
-
☠️ 危险函数示例
类型 | 示例 | 风险场景 |
---|---|---|
缓冲区操作 | printf , strtok |
静态缓冲区被覆盖 |
内存管理 | malloc , free |
内存池状态不一致 |
全局变量修改 | rand |
随机数序列破坏 |
安全实践
c
volatile sig_atomic_t flag = 0; // 原子标志
void handler(int sig) {
flag = 1; // 仅设置标志
}
int main() {
signal(SIGINT, handler);
while (1) {
if (flag) {
// 在主程序中安全处理
printf("Signal received\n");
flag = 0;
}
}
}
三、setjmp
/longjmp
机制
核心机制
-
setjmp(env)
:保存当前执行环境(寄存器/栈指针)到jmp_buf
- 首次调用返回
0
- 首次调用返回
-
longjmp(env, val)
:跳回setjmp
位置- 使
setjmp
返回val
(若val=0
则返回1
)
- 使
关键限制
-
资源泄露:跳过资源释放(文件句柄/内存不释放)
-
栈帧失效 :若
setjmp
所在函数已返回 → 未定义行为 -
变量回滚 :非
volatile
变量可能恢复初始值 -
信号不兼容:无法从信号处理函数跳转
应用场景
-
深度嵌套错误处理(替代层层返回错误码)
-
简易协程实现(用户级多任务)
四、大小端系统输出差异
关键代码
c
int x = 0x12345678;
char *p = (char*)&x; // p指向最低地址字节
printf("%x", *p); // 输出首字节值
内存布局差异
系统类型 | 存储顺序 (低地址→高地址) | 输出值 |
---|---|---|
小端 | 78 56 34 12 |
0x78 |
大端 | 12 34 56 78 |
0x12 |
现实系统示例
-
小端系统:x86/x64, ARM(默认), RISC-V
-
大端系统:PowerPC, SPARC, 网络字节序
-
检测方法:
c
printf(*p == 0x78 ? "Little-Endian" : "Big-Endian");
五、restrict
关键字
核心用途
-
指针独占性承诺 :向编译器保证指针是访问数据的唯一方式
-
优化触发:允许编译器进行激进优化(指令重排/寄存器缓存)
风险示例
c
void add(int *restrict a, int *restrict b, int *c) {
*a += *c; // 编译器假设b/c不重叠
*b += *c; // 若实际重叠→UB
}
正确使用场景
-
数学库函数(如
memcpy
实现) -
性能关键循环
六、未定义行为(UB) vs 实现定义行为
核心区别
特征 | 未定义行为 (UB) | 实现定义行为 |
---|---|---|
标准约束 | 无任何约束 | 编译器必须文档化 |
后果可预测性 | 完全不可预测 | 平台内一致 |
开发者应对 | 必须避免 | 需查阅编译器文档 |
示例 | 空指针解引用、有符号溢出 | sizeof(int) 、字节序 |
七、严格别名规则(Strict Aliasing)
核心规则
-
禁止通过不同类型指针 访问同一内存(除
char*
) -
优化目的:允许编译器基于类型假设优化内存访问
违规示例
c
int a = 10;
float *p = (float*)&a; // 违反规则!
printf("%f", *p); // UB
合法替代方案
-
使用
union
进行类型转换 -
通过
char*
访问 -
memcpy
复制字节
八、原子操作重要性
多线程问题根源
- 非原子操作(如
count++
)编译为多条指令 → 线程切换导致中间状态暴露
原子操作特性
特性 | 说明 |
---|---|
不可分割性 | 其他线程看到操作前/后的完整状态 |
内存屏障 | 保证指令执行顺序 |
无数据竞争 | 避免并发访问冲突 |
C11解决方案
c
#include <stdatomic.h>
atomic_int counter = 0;
atomic_fetch_add(&counter, 1); // 原子自增
九、_Generic
关键字
核心用途
-
编译时类型分发:根据表达式类型选择不同代码分支
-
C11泛型编程:模拟函数重载
应用示例
c
#define type_str(x) _Generic((x), \
int: "int", \
float: "float", \
char*: "string", \
default: "unknown" \
)
printf("%s", type_str(10)); // 输出"int"
printf("%s", type_str("text")); // 输出"string"
十、可变长度数组(VLA)
优点
-
栈分配效率 :比
malloc
更快(无堆管理开销)c
void func(int n) { int vla[n]; // 栈分配 }
-
语法简洁 :直接声明
int arr[n]
缺点
问题类型 | 说明 |
---|---|
栈溢出风险 | 大数组导致栈耗尽 |
不可初始化 | int arr[n] = {0} 非法 |
生存期限制 | 函数返回后数组失效 |
sizeof 行为 |
运行时计算,影响常量表达式 |
使用建议
-
小型临时数组用VLA
-
大型或长期存在数组用
malloc
+free
综合知识图谱
