【问题标题】:Is the compiler required to emit stores to raw addresses?编译器是否需要将存储发送到原始地址?
【发布时间】:2022-01-22 07:32:58
【问题描述】:

两个流行的编译器(gcc、clang)在以下函数的主体中发出存储指令:

void foo(char x) {
  *(char *)0xE0000000 = x;
}

此程序可能在某些硬件架构上正常运行,其中写入的地址是内存映射 IO。

由于此访问是通过不限定为volatile 的指针执行的,编译器是否需要在此处发出存储?一个足够积极的优化器可以合法地消除这家商店吗?我很好奇这个商店是否对抽象机器构成了可观察到的副作用。

另外,C17 和 C++20 在这方面有区别吗?

【问题讨论】:

  • 这取决于硬件以及编译器对底层硬件的理解程度。如果编译器已经编程有足够的知识,它知道地址没有任何特殊含义并且不会再次访问内存位置,那么肯定可以使用积极的优化来删除该代码。
  • 取消引用未分配的内容是 C++ 中的 implementation defined。 AFAIK C++ 在您使用它的意义上没有地址的概念。使用std::address_of 返回T*。因此,您在这里使用的“地址”是一种实现方式。
  • C 标准不需要存储,因为分配给非易失性左值不是 C 标准定义的可观察行为。
  • @Jellyboy 这实际上是我很好奇的上下文,但我不想用这些细节污染问题。 :) 在我的情况下,它是一个带有内存映射外围 IO 系统的 ARM Cortex-M 系列微,我很好奇是否需要 volatile。
  • @CharlesNicholson 鉴于已定义实现,我会说 yes 因为如果您稍后升级编译器,它可能会死存储优化它或 UB 优化它。这两个都被volatile阻止了。

标签: c++ c language-lawyer compiler-optimization


【解决方案1】:

C 标准不需要实现来发布商店,因为 C 2018 5.1.2.3 6 说:

对一致性实现的最低要求是:

- 对 volatile 对象的访问严格按照抽象机的规则进行评估。

——在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。

——交互设备的输入和输出动态应按照 7.21.3 中的规定进行。这些要求的目的是尽快出现无缓冲或行缓冲的输出,以确保在程序等待输入之前实际出现提示消息。

这是程序的可观察行为

这些都不包括对非易失性左值的赋值。

【讨论】:

  • 同样值得注意的是,即使使用volatile 也只能防止编译器级别相对于其他volatile 存储/读取的重新排序,它不会阻止指令或管道级别的重新排序。这可能需要使用内在函数。
  • 不是直接,@DavidSchwartz,但 C 规范确实说它根据抽象机器定义语言语义。易失性访问是一种副作用,相对于抽象机器上的其他副作用或执行的评估,该副作用不能重新排序,但是该语言对非抽象建模的易失性访问的任何影响无话可说机器。
  • @DavidSchwartz 您将标准与 CPU 架构混淆了。我正在描述一种导致 Linux 内核令人头疼的情况(并让他们使用 cpuid 指令解决它)。该标准具有 AS-IF,volatile 仅受此约束。但它不会锁定或阻止管道级别的重新排序。因此,如果写入设备的寄存器 A 必须在写入寄存器 B 之前发生,那么您需要一些东西来强制管道序列化。这不是标准的一部分。这是 RISC-V 和 AlphaAXP 的一个主要问题。
  • @DavidSchwartz 我对声明的措辞非常谨慎,甚至修改了它以指定它仅适用于volatile 读写。所以它适用于抽象状态机,即使这样volatile 也可以被提升(只需检查,clang 会这样做)。我的声明是提醒任何依赖 volatile 进行 IO 的人,CPU 不受标准限制。仅此而已。
【解决方案2】:

在 C 中,明确允许将整数转换为任何指针类型,但这并不意味着您可以使用生成的指针来访问内存。除非转换出现在空指针常量(不能用于访问内存)的上下文中,否则转换的结果

是实现定义的,可能没有正确对齐,可能不指向引用类型的实体,并且可能是一个陷阱表示。

(C17,6.3.2.3/5)

“实现定义”为后续访问所谓的指向对象留下了足够的空间,使其具有不涉及访问内存的行为,远远超出执行陷阱或展示未对齐访问的(未指定)效果甚至展示UB 礼貌的严格混叠违规。

只有通过所有这些,您才能解决@EricPostpischil 在his answer 中提出的非volatile 访问问题。

底线是,如果您正在编写一个独立的实现,这是唯一可以尝试执行示例代码中的访问的上下文,那么您需要咨询您的实现有关如何访问绝对地址的文档。

据我所知,同样的结论也适用于 C++。

【讨论】:

  • 这确实只是一个独立的实现。感谢您提供详细信息!
  • 为了非常迂腐,我选择“char *”作为这个例子的类型的原因是为了避免关于严格别名和 UB 雷区的可能切线的子讨论。不过,阅读您的回答让我怀疑我是否误解了?
  • @CharlesNicholson,我不认为所提供的具体示例会带来访问错位的风险,因为 C 定义了这一点,而且我知道严格别名方面很好,因为您正在通过char * 类型的指针。然而,这些风险确实适用于一般情况,并且它们是抽象机器可以对错误访问进行建模的一些方式。
  • 感谢详细回复;这也是我的理解。感谢您回来回复!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-01
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 2020-12-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多