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

综合知识图谱

相关推荐
西岭千秋雪_2 小时前
Zookeeper实现分布式锁
java·分布式·后端·zookeeper·wpf
MarcoPage3 小时前
Python 字典推导式入门:一行构建键值对映射
java·linux·python
脸大是真的好~3 小时前
黑马JAVAWeb-11 请求参数为数组-XML自动封装-XML手动封装-增删改查-全局异常处理-单独异常分别处理
java
顾安r5 小时前
11.8 脚本网页 星际逃生
c语言·前端·javascript·flask
Hello.Reader5 小时前
Data Sink定义、参数与可落地示例
java·前端·网络
im_AMBER6 小时前
React 17
前端·javascript·笔记·学习·react.js·前端框架
报错小能手6 小时前
C++笔记——STL map
c++·笔记
2401_837088506 小时前
stringRedisTemplate.opsForHash().entries
java·redis
LaoZhangGong1236 小时前
STM32 F103外部晶振8MHz改为12MHz,如何配置?
c语言·stm32·单片机·嵌入式硬件·晶振
lkbhua莱克瓦248 小时前
Java基础——集合进阶3
java·开发语言·笔记