C 易失性和非易失性位字段

C 易失性和非易失性位字段,c,arm,volatile,bit-fields,C,Arm,Volatile,Bit Fields,我正在为Cortex-M0CPU和gcc编写代码。我有以下结构: struct { volatile unsigned flag1: 1; unsigned flag2: 1; unsigned foo; // something else accessed in main loop } flags; flag1从GPIO中断处理程序和主循环读取和写入标记2仅在主循环中读取和写入 ISR如下所示: void handleIRQ(void) { if (!fla

我正在为Cortex-M0CPU和gcc编写代码。我有以下结构:

struct {
    volatile unsigned flag1: 1;
    unsigned flag2: 1;

    unsigned foo; // something else accessed in main loop
} flags;
flag1
从GPIO中断处理程序和主循环读取和写入<代码>标记2仅在主循环中读取和写入

ISR如下所示:

void handleIRQ(void) {
    if (!flags.flag1) {
        flags.flag1 = 1;
        // enable some hw timer
    }
}
for (;;) {
    // disable IRQ
    if (flags.flag1) {
        // handle IRQ
        flags.flag1 = 0;
        // access (rw) flag2 many times
    }
    // wait for interrupt, enable IRQ
}
主循环如下所示:

void handleIRQ(void) {
    if (!flags.flag1) {
        flags.flag1 = 1;
        // enable some hw timer
    }
}
for (;;) {
    // disable IRQ
    if (flags.flag1) {
        // handle IRQ
        flags.flag1 = 0;
        // access (rw) flag2 many times
    }
    // wait for interrupt, enable IRQ
}
当访问主循环中的
flag2
时,编译器是否会优化对它的访问,使它不会在每次以代码形式读取或写入时都被提取或存储到内存中


我不清楚,因为要在ISR中设置
flag1
,它需要加载整个
char
,设置一个位并将其存储回去。

仅一个位的volatile标志并没有那么有意义,甚至可能有害。实际上,编译器可能会分配两个内存块,每个内存块的宽度可能为32位。因为volatile标志阻止它在同一分配区域内组合两个位,因为没有可用的位级访问指令

在访问主循环中的flag2时,编译器是否会优化对它的访问,从而使它不会在每次以代码形式读取或写入时都被提取或存储到内存中

这很难说,取决于有多少数据寄存器可用。反汇编代码并查看

总的来说,不建议使用位字段,因为标准对它们的定义很差。在这种情况下,单个易失性位可能会导致分配额外的内存

相反,您应该这样做:

volatile bool flag1;
bool flag2;

假设这些标志不是硬件寄存器的一部分,在这种情况下,代码从一开始就不正确,并且它们都应该是易失性的。

根据我对C11标准的理解,使用位字段是不合适的,即使它们都被声明为易失性的。以下摘录自:

  • 内存位置
    要么是标量类型的对象,要么是宽度均非零的相邻位字段的最大序列
  • 注意1两个执行线程可以更新和访问独立的内存位置,而不会相互干扰

  • 注2如果同时更新同一结构中的两个非原子位字段,则不安全 它们之间声明的成员也是(非零长度)位字段,无论这些字段的大小如何 中间的位字段恰好是

  • volatile
    没有例外。因此,如果执行的两个线程(即主线程和ISR)都将更新一个标志,而主线程将更新另一个标志,则使用上述位字段是不安全的。给出的解决方案是在中间添加一个大小为0的成员,以强制将它们放置在不同的内存位置。但同样,这意味着这两个标志都将消耗至少一个字节的内存,因此对它们使用非位字段
    无符号字符
    bool
    更简单:

    struct {
        volatile bool flag1;
        bool flag2;
    
        unsigned foo; // something else accessed in main loop
    } flags;
    
    现在,它们将被放置在不同的内存位置,并且可以在不相互干扰的情况下进行更新


    然而,
    flag1
    volatile
    仍然是严格必需的,因为否则对
    flag1
    的更新在主线程中将不会产生副作用,编译器可以推断它只能将该字段保留在寄存器中,或者根本不需要更新

    但是,需要注意的是,在C11下,即使是
    volatile
    的保证也可能不够:

    当接收到信号而中断抽象机器的处理时,既不是无锁原子对象也不是volatile sig_atomic_t类型的对象的值是未指定的,浮点环境的状态也是如此。当处理程序退出时,处理程序修改的任何对象(既不是无锁原子对象,也不是volatile sig_atomic__t类型)的值将变得不确定,如果浮点环境被处理程序修改且未恢复到其原始状态,则浮点环境的状态也将不确定

    因此,如果需要完全兼容,
    flag1
    应该是例如类型
    volatile\u-Atomic bool
    ;甚至可以使用
    \u原子
    位字段。然而,这两种方法都需要C11编译器


    然后,您可以再次检查编译器的手册,如果它们保证对此类易失性对象的访问也是原子的。

    什么是
    sizeof(结构标志)
    ?我已经更新了答案。位字段后面有一个int。所以大小应该是8。您很可能不想为此使用位字段。它们是执行定义行为的雷区。即使这是在某处定义的,我也不会相信编译器会做正确的事情。@user694733-这是一个奇怪的位置。如果它是由标准定义的,那么为什么要信任编译器来实现标准所说的任何其他东西呢?如果它是实现定义的,那么如果你不信任它的文档,为什么要使用它呢?@StoryTeller我的说法很糟糕。应该把最后一句话删掉。我的意思是;即使它是由编译器供应商(不是C标准)定义并在编译器手册中提到的,它也可以在下一次主要的编译器更新中更改,并且在更新过程中很容易被忽略。您在标准中从何处找到此文本?我想是的。注释不是ISO标准中的规范性文本!但是注释当然是正确的,这是一些奇怪的代码。@AnttiHaapala注释、脚注和示例不规范。此外,C语言未指定对位字段使用
    无符号字符
    。该语言仅允许
    \u Bool
    和(有符号/无符号)
    int
    。因此,如果编译器完全接受“修复”的话,就不知道“修复”会做什么。根据我的理解,volatile不会阻止它们进入同一个内存位置