挥发性和序列点

Volatile and sequence point

给定以下代码:

unsigned int global_flag = 0;

void exception_handle()
{
    global_flag = 1;
}

void func()
{
    /* access will cause exception which will assign global_flag = 1
       then execution continues */
    volatile unsigned int x = *(unsigned int *)(0x60000000U); /* memory protection unit configured to raise exception upon accessing this address */

    if (global_flag == 1)
    {
        /* some code */
    }
}

鉴于 volatile must not be reordered across sequence points:

The minimum requirement is that at a sequence point all previous accesses to volatile objects have stabilized and no subsequent accesses have occurred

并给出以下关于 sequence points 的信息:

sequence points occur in the following places ... (1) .. (2) .. (3) At the end of a full expression. This category includes expression statements (such as the assignment a=b;), return statements, the controlling expressions of if, switch, while, or do-while statements, and all three expressions in a for statement.

是否承诺volatile unsigned int x = *(unsigned int *)(0x60000000U);会在if (global_flag == 1)之前发生(在二进制asm中,CPU乱序执行与这里无关)?

根据上面的引文,volatile unsigned int x = *(unsigned int *)(0x60000000U);必须在下一个序列点结束之前计算,而volatile unsigned int x = *(unsigned int *)(0x60000000U);本身就是一个序列点,所以这意味着每个volatile 赋值是在赋值时求值的?

如果上述问题的答案是否定的,那么下一个序列点在ifend,是否意味着可以执行类似的事情:

if (global_flag == 1)
{
    volatile unsigned int x = *(unsigned int *)(0x60000000U);
    /* some code */
}

系统是嵌入式one-ARM cortex m0,单核,单线程应用。

Is it promised that volatile unsigned int x = *(unsigned int *)(ILLEGAL_ADDRESS); will take place before if (global_flag == 1)

来自信息性 C11 AnnexC(添加 newlines/formatting 以提高可读性):

The following are the sequence points described in 5.1.2.3:
...
- Between the evaluation of a full expression and the next full expression to be evaluated.
- The following are full expressions:
- an initializer that is not part of a compound literal (6.7.9);
- the expression in an expression statement (6.8.3);
- the controlling expression of a selection statement (if or switch) (6.8.4);
- the controlling expression of a while or do statement (6.8.5);
- each of the (optional) expressions of a for statement (6.8.5.3);
- the (optional) expression in a return statement (6.8.6.4).

因为 *(unsigned int *)(ILLEGAL_ADDRESS); 是一个初始化器(赋值表达式)并且初始化器不是复合文字的一部分,所以它是一个完整的表达式。下一个完整表达式是 if 中的控制语句,因此在 ifx 的初始化之间有一个序列点。

来自著名的C11 5.1.2.3p6

The least requirements on a conforming implementation are:

Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
...

由于x是一个volatile对象,它被严格初始化为抽象机,所以在序列点之后它的右值必须等于*(unsigned int *)(ILLEGAL_ADDRESS)操作的结果。

所以是的,x 对象的初始化必须发生在 if.

中的控制表达式之前

关于未定义的行为,引用自C11 6.5.3.2p4

If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

正如您评论的那样:

accessing address 0x60000000 is not permitted in my system memory model

可以推断出 (unsigned int*)0x60000000 是无效指针,因此一元 * 运算符应该 spawn dragons.

在您的代码片段中,变量 global_flag 不是易变的,因此没有什么可以阻止编译器跨序列点移动对 global_flag 的访问,或者如果情况允许,则完全删除它。讨论访问x和访问global_flag的顺序是没有意义的,因为后者不是可观察事件,只有前者是。

(另请注意,表达式 *(unsigned int *)(0x60000000U) 中没有 volatile 限定符。我认为这确实是您希望特别对待的表达式,但您的代码不会那样做。允许编译器生成预先评估 *(unsigned int *)(0x60000000U) 的代码,然后执行其盘子上的大量其他内容,然后将获得的值分配给 x,这将满足以下约束C 标准放在 volatile 左值上。)

如果您的代码段有 unsigned int volatile global_flag = 0;*(volatile unsigned int *)(0x60000000U),那么“是否承诺……”这个问题的答案将是明确的“是”。