一、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
综合知识图谱