5.1.2.3 节第 4 段(在 n1570.pdf 中)对空操作意味着什么?

What does section 5.1.2.3, paragraph 4 (in n1570.pdf) mean for null operations?

我多次被告知无法优化对 volatile 对象的访问,但在我看来,C89、C99 和 C11 标准中的本节另有建议:

... An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

如果我理解正确,这句话是在说明实际实现 可以 优化表达式的一部分,前提是满足以下两个要求:

在我看来,很多人将 "including" 的含义与 "excluding" 的含义混淆了。

编译器是否可以区分 "needed" 的副作用和不是 "needed" 的副作用?如果计时被认为是一个需要的副作用,那么为什么允许编译器优化空操作,如 do_nothing();int unused_variable = 0;

如果编译器能够推断出某个函数什么都不做(例如 void do_nothing() { }),那么编译器是否有理由优化对该函数的调用?

如果编译器能够推断出 volatile 对象没有映射到任何重要的东西(即它可能映射到 /dev/null 以形成空操作),那么是否有可能编译器可能也有理由优化那个非关键的副作用?

如果编译器可以执行优化以消除不必要的代码,例如在称为 "dead code elimination" 的进程中调用 do_nothing()(这是很常见的做法),那么为什么编译器也不能消除对空设备的易失性写入?

据我了解,要么编译器可以优化掉对函数或易失性访问的调用,要么编译器不能优化掉, 因为 5.1.2.3p4.

我认为“包括任何”适用于“所需的副作用”,而您似乎将其解读为适用于“表达式的一部分”。

所以本意是说:

... An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced .

Examples of needed side-effects include:

  • Needed side-effects caused by a function which this expression calls
  • Accesses to volatile variables

现在,术语需要的副作用 未由标准定义。第 /4 节也没有尝试对其进行定义——它正在尝试(但不太成功)提供示例。

我认为唯一明智的解释是将其视为可观察到的行为,这是由 5.1.2.3/6 定义的。所以写起来会简单得多:

An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no observable behaviour would be caused.


你在编辑中的问题由 5.1.2.3/6 回答,有时被称为 as-if 规则,我将在这里引用它:

The least requirements on a conforming implementation are:

  • Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
  • At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.
  • The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.

This is the observable behaviour of the program.

回答编辑中的具体问题:

Is it possible for a compiler to distinguish between a side effect that's "needed", and a side effect that isn't? If timing is considered a needed side effect, then why are compilers allowed to optimise away null operations like do_nothing(); or int unused_variable = 0;?

时机不是副作用。 “需要的”副作用大概是指导致可观察到的行为的副作用。

If a compiler is able to deduce that a function does nothing (eg. void do_nothing() { }), then is it possible that the compiler might have justification to optimise calls to that function away?

是的,可以优化这些,因为它们不会导致可观察到的行为。

If a compiler is able to deduce that a volatile object isn't mapped to anything crucial (i.e. perhaps it's mapped to /dev/null to form a null operation), then is it possible that the compiler might also have justification to optimise that non-crucial side-effect away?

否,因为对易失性对象的访问被定义为可观察的行为。

If a compiler can perform optimisations to eliminate unnecessary code such as calls to do_nothing() in a process called "dead code elimination" (which is quite the common practice), then why can't the compiler also eliminate volatile writes to a null device?

因为易失性访问被定义为可观察的行为,而空函数则不是。

我相信这一点:

(including any caused by calling a function or accessing a volatile object)

意为

(including:

  • any side-effects caused by calling a function; or
  • accessing a volatile variable)

这种解读是有道理的,因为访问易失性变量一个副作用。