C语言中volatile与变量修改:中断/非中断函数操作结构体的问询
首先得明确你遇到的核心问题:编译器不知道func2会被中断调用,所以它可能会对主上下文(非中断)里访问结构体成员的代码做优化——比如把inc1的值缓存到寄存器里,而中断里修改了内存中的实际值后,主上下文还在使用寄存器里的旧值,导致数据不一致。
解决这个问题的关键就是利用volatile关键字,它的作用就是告诉编译器:这个变量(或指针指向的内容)可能会被当前执行流之外的代码(比如中断)意外修改,绝对不能对它做任何缓存优化,每次访问都必须直接读写内存。
具体怎么做呢?分两步来:
1. 给共享的结构体变量加上volatile修饰
因为你的TsMyStruct实例是被主上下文(func1)和中断上下文(func2)共享的,所以在定义这个变量的时候要加上volatile:
// 全局共享变量,声明为volatile volatile TsMyStruct g_mySharedStruct;
这样编译器就会知道,这个变量的值随时可能被中断修改,不会再把它的成员缓存到寄存器里,每次读写都会直接操作内存,保证拿到的是最新值。
2. 调整函数参数为volatile指针
如果直接把volatile变量的地址传给普通的TsMyStruct*参数,编译器可能会抛出类型不兼容的警告,而且函数内部操作成员时,编译器还是可能偷偷做优化。所以要把func1和func2的参数改成volatile TsMyStruct*:
void func1(volatile TsMyStruct* myStruct) { myStruct->inc1 += 1; } void func2(volatile TsMyStruct* myStruct) { myStruct->inc1 += 2; myStruct->inc2 += 3; }
这样函数内部访问结构体成员时,会严格遵守volatile的规则,不会做任何优化。
额外提醒:volatile不保证原子性
这里要注意,volatile只解决内存可见性的问题,比如让主上下文能看到中断修改后的值,或者中断能看到主上下文修改后的值。但像inc1 += 1这种操作是「读-修改-写」三步,如果中断刚好在主上下文执行这三步的中间打断,还是可能出现数据错误。如果要解决原子性问题,你可能需要在主上下文操作时临时禁用中断,或者使用CPU提供的原子指令,但这已经超出volatile的职责范围了。
可选:只给共享成员加volatile
如果你的结构体里只有部分成员是被共享的(比如只有inc1被主上下文和中断同时访问,inc2只有中断会修改且主上下文不读取),也可以只给特定成员加volatile:
typedef struct { volatile uint16_t inc1; // 共享成员加volatile uint16_t inc2; // 非共享成员不需要 } TsMyStruct;
不过这种方式灵活性稍差,如果你之后主上下文需要访问inc2,还要再回来修改结构体定义,所以更推荐给整个共享变量加volatile的方式。
内容的提问来源于stack exchange,提问作者rem

