【问题标题】:Global Variable Access Relative to Function Calls and Returns相对于函数调用和返回的全局变量访问
【发布时间】:2016-09-09 16:57:44
【问题描述】:

我一直在研究这个话题,找不到具体的权威答案。我希望非常熟悉 C 规范的人可以回答 - 即确认或反驳我的断言,最好是引用规范。

断言: 如果一个程序包含多个编译单元(单独编译的源文件),编译器必须确保在调用另一个单元中的函数之前或从任何函数返回之前将全局变量(如果已修改)写入内存。此外,在任何函数中,必须在第一次使用之前读取全局。同样在调用任何函数之后,不在同一个单元中,全局必须在使用前读取。无论变量是否被限定为“易失性”,这些事情都必须是真实的,因为另一个编译单元(源文件)中的函数可以在编译器不知情的情况下访问该变量。否则,全局变量总是需要“易失性” - 即非易失性全局变量没有任何用途。

编译器能否以不同的方式处理同一编译单元中的函数?我为全局变量的“volatile”限定符找到的所有讨论都显示了同一编译单元中的所有函数。

编辑:编译器无法知道其他单元中的函数是否使用全局。因此,我假设上述条件。

我发现了另外两个问题,其中包含与该主题相关的信息,但他们没有直接解决问题,或者他们提供了我认为可疑的信息:

Are global variables refreshed between function calls?

When do I need to use volatile in ISRs?

【问题讨论】:

  • 为什么编译器必须确保在函数返回之前将全局变量写入内存?这实际上取决于 CPU 内核及其缓存处理。
  • 您提供的两个链接都表明,如果没有 volatile,则无法保证该变量将永远从内存中读取。
  • @Riley,我知道他们是这么说的。出于我所说的原因,我相信他们一定是错的。
  • @Joachim,我的问题是指 C“虚拟机”和硬件内存缓存不起作用。
  • @PaulB C 不使用虚拟机。

标签: c global-variables volatile


【解决方案1】:

[..] 在任何函数中,必须在第一次使用之前读取全局。

绝对不是:

static int variable;
void foo(void) {
  variable = 42;
}

为什么编译器要费心生成代码来读取变量?

编译器必须确保在任何函数调用之前或从函数返回之前将全局变量写入内存。

不,为什么要这样?

void bar(void) {
   return;
}
void baz(void) {
  variable = 42;
  bar();
}

bar 是一个纯函数(对于一个体面的编译器来说应该是可确定的),因此在函数调用后写入内存时不会出现任何不同的行为。

不过,“从函数返回之前”的情况很棘手。但我认为,如果我们也计算内联(静态)函数,那么一般性陈述(“必须”)是错误的。

编译器能否以不同的方式处理同一编译单元中的函数?

是的,我想是的:对于一个静态函数(其地址从未被占用),编译器确切地知道它是如何使用的,并且此信息可用于应用一些更激进的优化。

以上所有内容均基于第 5.1.2.3/6 节 (N1570) 中指定的 C 版本的 As-If 规则:

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

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

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

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

这是程序的可观察行为。

特别是,您可能想阅读以下“示例 1”。

【讨论】:

  • 我想我使用“必须”这个词引起了错误的反应。我对有多个编译单元的更常见的情况更感兴趣。而且我可能错误地认为 C 会以一种通用的方式处理所有函数。但是,我承认这可能不是真的。
  • 编译器如何知道另一个编译单元中的函数没有调用“bar”或“baz”?另一个单元的编译器如何知道“baz”或“bar”没有修改或使用“变量”?我认为链接器甚至有可能在以后替换不同版本的“baz”和/或“bar”。
猜你喜欢
  • 2018-08-31
  • 1970-01-01
  • 2017-06-09
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-06
  • 1970-01-01
相关资源
最近更新 更多