MSVC:为啥“#pragma fenv_access (on)”会导致“错误 C2099:初始化程序不是常量”?

Posted

技术标签:

【中文标题】MSVC:为啥“#pragma fenv_access (on)”会导致“错误 C2099:初始化程序不是常量”?【英文标题】:MSVC: why "#pragma fenv_access (on)" causes "error C2099: initializer is not a constant"?MSVC:为什么“#pragma fenv_access (on)”会导致“错误 C2099:初始化程序不是常量”? 【发布时间】:2021-02-08 23:35:11 【问题描述】:

示例代码(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?原因/动机是什么?

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

【问题讨论】:

【参考方案1】:

来自documentation:

编译器禁用浮点优化,因此您的代码可以一致地访问浮点环境。 ... 受 fenv_access 约束的优化类型有:

全局公共子表达式消除

代码运动

不断折叠

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

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

【讨论】:

【参考方案2】:

fenv_access pragma 文档特别指出:

编译器禁用浮点优化,因此您的代码可以一致地访问浮点环境。

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

float d = 0.0 + 0.0;

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

【讨论】:

【参考方案3】:

如果#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 是一个常量表达式。 因此,在支持附件 F 浮点语义 (IEC 60559/IEEE 754) 的实现中,0.0 + 0.0 将在翻译时进行评估(好像),而不影响执行时的浮点环境。

C11,§F.8.4 常量表达式,¶1(已添加重点):

浮点类型的算术常量表达式,在具有静态或线程存储持续时间的对象的初始化程序中除外,在执行期间被评估(好像)......

示例

#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:

…所有用于初始化具有静态或线程存储持续时间的对象的计算都(好像)在转换时完成。

示例

#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 已打开,在具有静态存储持续时间的对象的初始化程序中也允许使用涉及浮点运算的常量表达式。

【讨论】:

即使 Microsoft 声称他们的 C 编译器符合 C99 或 C11(这与实际情况相去甚远:仅声称符合 C90),那么编译器没有损坏 因为#pragma (C11, 6.10.6) 的定义包括...导致实现以实现定义的方式运行。我猜想偏离标准行为的需要是引入他们自己的 pragma #pragma fenv_access 而不是使用标准 pragma #pragma STDC FENV_ACCESS 的原因。 顺便说一句,C90没有提到#pragma STDC。在 C90 中,任何 #pragma都会导致实现以实现定义的方式运行 正确,他们完全有权根据标准解释 #pragma fenv_access (on)#pragma STDC FENV_ACCESS ON 不同——尽管这可能会令人困惑。这就是为什么我的回答是以它们的意思是相同的假设为前提的(这可能是正确的,也可能不是正确的);这个问答页面已经出现在诸如“pragma fenv_access float constant”之类的查询的网络搜索结果中,所以我认为这将是有用的附加信息。

以上是关于MSVC:为啥“#pragma fenv_access (on)”会导致“错误 C2099:初始化程序不是常量”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MSVC 不在生成的汇编代码中分配 32 字节的影子空间?

MSVC 中的地址消毒器:为啥它在启动时报告错误?

为啥 MSVC12 的自动矢量化器不分析函数模板?

为啥在 __assume 中使用函数调用时 MSVC 不报错?

为啥 MSVC 使用 SSE2 指令来处理这种琐碎的事情?

为啥 MSVC 在编译宏时大发雷霆,而 G++ 完全是关于禅的?