AVR平台使用volatile uint8_t替代volatile sig_atomic_t的合法性及相关疑问
1. 用volatile uint8_t替代volatile sig_atomic_t是否合法?
合法。
AVR是8位单片机,uint8_t类型的单次读写操作是原子性的——CPU能在一个总线周期内完成8位数据的读取或赋值,不会被中断打断,完全满足C标准对sig_atomic_t的核心要求:保证中断上下文与主上下文之间的变量访问不会出现数据撕裂。加上volatile关键字后,能防止编译器优化掉对该变量的读写,确保中断修改的值能被主程序及时感知,反之亦然。
2. 使用__SIG_ATOMIC_TYPE__是否合法?
合法,且更贴合标准设计意图。__SIG_ATOMIC_TYPE__是AVR-GCC提供的内置编译器宏,它的本质就是适配AVR架构的原子数据类型(在8位AVR上就是uint8_t)。这个宏是为了弥补部分环境中标准sig_atomic_t未定义的问题,用它定义变量既符合编译器规范,又能体现代码是针对原子操作场景设计的,比直接用uint8_t更具语义性。
3. 是否需要引入cli()/sei()宏?
需要,针对func()中的读-改-写操作必须加。
看你的func()函数里的flag = !flag;,这是一个读-修改-写的复合操作:先读取flag的值,取反后再写回。这个过程不是原子的——如果在读取和写回之间触发中断,ISR修改flag的值就会导致竞态条件(比如主程序读到flag=1,取反为0,还没写回时中断把flag设为1,主程序写回0就会覆盖中断的修改,导致逻辑错误)。
正确的做法是在func()中操作flag前后关/开中断:
void func(void) { cli(); // 关中断,禁止中断打断操作 for (uint8_t i=0; i<20; i++) { flag = !flag; } sei(); // 开中断,恢复中断响应 }
而ISR中的flag = 1;是单次赋值,本身是原子操作,加上ISR执行时AVR-GCC默认会自动关中断(通过__signal__属性实现),所以ISR内部不需要额外调用cli()/sei()。
内容的提问来源于stack exchange,提问作者wimalopaan

