示例代码(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
不允许进行常数折叠吗?
来自文档:
编译器禁用浮点优化,因此您的代码可以一致地访问浮点环境。
…
适用于fenv_access的优化类型有:
全局公共子表达式消除
代码运动
常数合并
将1.0 + 1.0
作为常数处理是常数折叠的一个例子。
尽管语言指定这是一个常量表达式,因此它可以用作静态初始化器,但编译程序会重写此处理,因为它需要在运行时执行加法,以防设置浮点标志。
fenv_access
pragma文档特别说明:
编译器禁用浮点优化,因此您的代码可以一致地访问浮点环境。
因此,看起来禁用的优化之一是常量的折叠,这意味着:
float d = 0.0 + 0.0;
需要在运行时而不是编译时计算。所以编译器是正确的,在这种情况下它不是一个常量。
如果#pragma fenv_access (on)
应该具有与#pragma STDC FENV_ACCESS ON
相同的效果,那么编译器在C99和C11标准方面是坏的。然而,微软可能并不关心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,也允许在具有静态存储时间的对象的初始化器中使用涉及浮点运算的常量表达式。