1

在这个例子中:

volatile uint32_t * pOne = 0xDEADBEEF;
volatile uint32_t * pTwo = 0x0BADC0DE;

void same(void)
{
    uint32_t tmp;

    tmp = *pOne;   // A
    *pOne = 0;     // B
    *pOne = tmp;   // C
}

void different(void)
{
    uint32_t tmp;

    tmp = *pOne;
    *pOne = 0;     // E
    *pTwo = 0;     // F
    *pOne = tmp;
}

据我所知,C99 编译器不允许对 lines和in重新排序A,因为它们都引用同一个 volatile 对象。 但是线条和功能呢?它们与不同的易失性对象交互。BCsame()
EFdifferent()

  1. 是否允许 C99 编译器重新排序行EF

我无法在标准本身中找到答案,因为第 5.1.2.3 节让我有点困惑。所以如果你能解释一下,我会很高兴的。

我知道这仅涉及编译器的重新排序,不会影响处理器的任何重新排序。

  1. 那么是否有一个标准库(如果实现)提供内存屏障?

  2. 目前我坚持使用C99,但出于好奇:C11有什么变化吗?

4

2 回答 2

0

从规范(6.7.3.6)

具有 volatile 限定类型的对象可能会以实现未知的方式被修改或具有其他未知的副作用。因此,任何引用此类对象的表达式都应严格按照抽象机的规则进行评估,如 5.1.2.3 中所述。此外,在每个序列点,最后存储在对象中的值应与抽象机规定的值一致,除非由前面提到的未知因素修改。构成对具有 volatile 限定类型的对象的访问是实现定义的。

在大多数(如果不是全部)实现中,获取/设置该值将被视为“访问”。

从 5.1.2.3 开始:

访问 volatile 对象、修改对象、修改文件或调用执行任何这些操作的函数都是副作用,它们是执行环境状态的变化。表达式的评估可能会产生副作用。在被称为序列点的执行序列中的某些指定点,之前评估的所有副作用都应该是完整的,并且后续评估的副作用应该没有发生。(序列点的摘要在附件 C 中给出。)

在抽象机中,所有表达式都按照语义的规定进行评估。如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。

每个赋值都是一个序列点,所以如果一个实现没有从 volatile 访问中“推断出不会产生任何需要的副作用”,那么这意味着这些行不能在一个实现中重新排序,因为这不会具有相同的语义意义。

当然,许多编译器并非 100% 符合标准。

在这里,您可以看到各种编译器的汇编器输出:https ://godbolt.org/z/b0TNmT 。

GCC、Clang 和 MSVC 都不会重新排序汇编中的读取和写入。(虽然我不确定这对实际可执行文件意味着什么)

于 2018-09-05T19:06:54.267 回答
0

volatile在对象的语义方面允许实现相当大的自由裁量权。程序的可观察行为必须与已按指定顺序执行的所有易失性对象的所有操作一致,但实现具有执行几乎任何不影响程序可观察行为的所有操作的全面许可。实现还具有相当广泛的权限,可以在“实现定义的行为”领域内指定 volatile 限定访问的哪些方面是“可观察的”和不可观察的。

一个不适合嵌入式编程的符合实现可以指定volatile- 限定访问将以怪异和任意的方式表现,这将使其无法用于此类目的。在大多数平台上,应该相当清楚,旨在允许嵌入式编程而不使用编译器特定指令的高质量实现应该如何处理volatile-qualified 访问,并且旨在用于此类用途的高质量实现应该以这种方式处理它们,即使本标准不要求他们这样做。不幸的是,一些编译器编写者将他们的语义限制在标准明确要求的范围内,而不是扩展他们的语义以匹配底层平台(例如,当针对平台时volatile访问的行为可能类似于子程序调用,不要对volatile访问中的任何操作重新排序,除非它可以在对未知函数的调用中重新排序)。虽然icc似乎以volatile这种方式处理 -qualified 写入,但编译器喜欢gcc并且clang不适合用于启用优化的嵌入式系统,只有在大多数优化被禁用时才会这样做。

于 2018-09-04T22:51:39.460 回答