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

综合知识图谱

相关推荐
用户849137175471635 分钟前
JDK 17 实战系列(第3期):性能优化与系统增强详解
java·后端·性能优化
MUTA️36 分钟前
《CogAgent: A Visual Language Model for GUI Agents》论文精读笔记
人工智能·笔记·语言模型·多模态
Asu52021 小时前
思途spring学习0807
java·开发语言·spring boot·学习
遇见火星1 小时前
Jenkins全链路教程——Jenkins用户权限矩阵配置
java·矩阵·jenkins
埃泽漫笔1 小时前
什么是SpringBoot
java·spring boot
zhang1062091 小时前
PDF注释的加载和保存的实现
java·开发语言·pdf·pdfbox·批注
码银2 小时前
什么是逻辑外键?我们要怎么实现逻辑外键?
java·数据库·spring boot
SugarFreeOixi2 小时前
Idea打包可执行jar,MANIFEST.MF文件没有Main-Class属性:找不到或无法加载主类
java·jar
Mr Aokey2 小时前
从BaseMapper到LambdaWrapper:MyBatis-Plus的封神之路
java·eclipse·mybatis
小白学大数据2 小时前
Java爬虫性能优化:多线程抓取JSP动态数据实践
java·大数据·爬虫·性能优化