C语言深度语法掌握笔记:底层机制,高级概念

一、printf%n格式说明符

作用
  • 写入输出字符数:将当前已输出的字符数量写入参数指定的内存地址

    c

    复制代码
    int count;
    printf("Hello%n", &count);  // 输出"Hello"后count=5
风险
  • 格式化字符串攻击:若用户控制格式字符串,可向任意地址写入数据

    c

    复制代码
    // 危险代码!
    printf(user_input); // 用户输入"%100x%n"可覆盖返回地址
防御措施
  1. 禁用%n:使用printf_s等安全版本

  2. 隔离用户输入: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
关键限制
  1. 资源泄露:跳过资源释放(文件句柄/内存不释放)

  2. 栈帧失效 :若setjmp所在函数已返回 → 未定义行为

  3. 变量回滚 :非volatile变量可能恢复初始值

  4. 信号不兼容:无法从信号处理函数跳转

应用场景
  • 深度嵌套错误处理(替代层层返回错误码)

  • 简易协程实现(用户级多任务)


四、大小端系统输出差异

关键代码

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
合法替代方案
  1. 使用union进行类型转换

  2. 通过char*访问

  3. 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)

优点
  1. 栈分配效率 :比malloc更快(无堆管理开销)

    c

    复制代码
    void func(int n) {
      int vla[n];  // 栈分配
    }
  2. 语法简洁 :直接声明int arr[n]

缺点
问题类型 说明
栈溢出风险 大数组导致栈耗尽
不可初始化 int arr[n] = {0} 非法
生存期限制 函数返回后数组失效
sizeof行为 运行时计算,影响常量表达式
使用建议
  • 小型临时数组用VLA

  • 大型或长期存在数组用malloc+free

综合知识图谱

相关推荐
空空kkk几秒前
SSM项目练习——hami音乐(三)
java·数据库
啟明起鸣几秒前
【Nginx 网关开发】上手 Nginx,简简单单启动一个静态 html 页面
运维·c语言·前端·nginx·html
vortex53 分钟前
深度字典攻击(实操笔记·红笔思考)
前端·chrome·笔记
卡里笔记4 分钟前
C语言版2048小游戏
c语言
闪闪发亮的小星星6 分钟前
主旋参数(四元数)与欧拉参数
笔记·其他
爬山算法9 分钟前
Hibernate(78)如何在GraphQL服务中使用Hibernate?
java·hibernate·graphql
独断万古他化13 分钟前
【Spring 核心:AOP】基础到深入:思想、实现方式、切点表达式与自定义注解全梳理
java·spring·spring aop·aop·切面编程
梵刹古音20 分钟前
【C语言】 循环结构
c语言·开发语言·算法
编程彩机26 分钟前
互联网大厂Java面试:从分布式事务到微服务优化的技术场景解读
java·spring boot·redis·微服务·面试·kafka·分布式事务
bbq粉刷匠27 分钟前
Java-排序2
java·数据结构·排序算法