MSVC:为什么“#pragma fenv_access (on)”会导致 "error C2099: initializer is not a constant"?

MSVC: why "#pragma fenv_access (on)" causes "error C2099: initializer is not a constant"?

示例代码(t50.c):

#pragma fenv_access (on)
float d = 0.0 + 0.0;
int main(void)
{
    return 0;
}

调用:

$ cl t50.c /fp:strict
Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

t50.c
t50.c(2): error C2099: initializer is not a constant

问题:为什么 #pragma fenv_access (on) 导致 error C2099: initializer is not a constant?原因/动机是什么?

是不是因为on #pragma fenv_access (on) cl不允许进行常量折叠?

来自documentation

The compiler disables floating-point optimizations, so your code can access the floating-point environment consistently.
...
The kinds of optimizations that are subject to fenv_access are:

  • Global common subexpression elimination

  • Code motion

  • Constant folding

1.0 + 1.0视为常量是常量折叠的一个例子。

虽然语言指定这是一个常量表达式,因此它可以用作静态初始值设定项,但 pragma 覆盖了这种处理,因为它需要在运行时执行加法,以防它设置浮点标志。

fenv_access pragma 文档明确指出:

The compiler disables floating-point optimizations, so your code can access the floating-point environment consistently.

因此看起来禁用的优化之一是常量的折叠,这意味着:

float d = 0.0 + 0.0;

需要在 运行 时而不是编译时计算。所以编译器是正确的,在那种情况下它不是常量。

如果 #pragma fenv_access (on) 应该具有与 #pragma STDC FENV_ACCESS ON 相同的效果,那么编译器就 C99 和 C11 标准而言是错误的。 但是,Microsoft 可能不关心 C 标准,并且 MSVC 可能对 #pragma fenv_access (on) 的解释与标准 pragma 不同。 该答案解决了 #pragma STDC FENV_ACCESS ON.

之后标准是否允许的问题

在顶层,

float d = 0.0 + 0.0;

声明一个对象 d,具有静态存储持续时间和初始值设定项 0.0 + 0.0。 表达式 0.0 + 0.0 是一个常量表达式。 因此,在支持 Annex F 浮点语义 (IEC 60559/IEEE 754) 的实现中,0.0 + 0.0 将在翻译时(好像)在执行时对浮点环境没有影响进行评估.

C11,§F.8.4 常量表达式,¶1(添加了强调):

An arithmetic constant expression of floating type, other than one in an initializer for an object that has static or thread storage duration, is evaluated (as if) during execution…

EXAMPLE

#include <fenv.h>
#pragma STDC FENV_ACCESS ON
void f(void)
{
  float w[] = { 0.0/0.0 };    // raise an exception
  static float x = 0.0/0.0;   // does not raise an exception
...

C11,§F.8.5 初始化,¶1:

…All computation for initialization of objects that have static or thread storage duration is done (as if) at translation time.

EXAMPLE

#include <fenv.h>
#pragma STDC FENV_ACCESS ON
void f(void)
{
  float u[] = { 1.1e75 };    // raises exceptions
  static float v = 1.1e75;   // does not raise exceptions
...

这些示例表明,即使 FENV_ACCESS 已打开,在具有静态存储持续时间的对象的初始化程序中也允许使用涉及浮点运算的常量表达式。