我对C标准的解释有问题,最新草案取自http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2454.pdf.
标准评估
本标准定义了pragma STD FENV_ACCESS
并规定(7.6.1p2):
The FENV_ACCESS pragma provides a means to inform the implementation when a program might
access the floating-point environment to test floating-point status flags or run under non-default
floating-point control modes.
目前尚不清楚为什么这个杂注需要在非默认浮点控制模式下运行。是因为吗
- 设置这些非默认模式需要写入控制模式寄存器,或者
- 即使已经设置了非默认的当前模式,pragma始终是必要的
在标准的这段后面,我们发现:
If part of a program tests floating-point status flags or establishes non-default floating-point
mode settings using any means other than the FENV_ROUND pragmas, but was translated with the
state for the FENV_ACCESS pragma "off", the behavior is undefined.
看起来在不更改的情况下测试当前模式并不是一种未定义的行为。但同一段的脚注指出:
In general, if the state of FENV_ACCESS is "off", the translator can assume that the flags are
not tested, and that default modes are in effect, except where specified otherwise by an
FENV_ROUND pragma.
问题
所以,如果没有指定杂注FENV_ACCESS,这是否意味着默认舍入模式有效?
让我们假设pragma FENV_ROUND也不存在,并且编译器假设默认情况下关闭了FENV_ACCESS,这对于向后兼容性是必要的。
示例
考虑以下源代码:
#include <math.h>
float func_01(float x) {
return nearbyint(x);
}
函数nearbyint
被描述为(7.12.9.3)使用当前舍入模式进行舍入。但该代码没有pragma FENV_ACCESS
。这是否意味着可以忽略当前舍入模式,并且nearbyint
与roundeven
相同?
C草案n2454
我根据2018年的现行C标准写下了这个答案,但问题是关于即将出台的标准的草案。在审查草案时,有重大变化,这个答案不适用。
值得注意的是,草案n2454在7.6.1 2中规定:
…如果程序的一部分使用
FENV_ROUND
杂注以外的任何方式测试浮点状态标志或建立非默认浮点模式设置,但使用FENV_ACCESS
杂注的状态进行了转换";"关";,行为未定义…
值得注意的是,C 2018中的这段文字出现在"非默认浮点模式设置"之后:
…或在非默认模式设置下运行,…
C 2018文本意味着,如果在FENV_ACCESS
打开的情况下编译的代码设置了非默认模式,并在FENV_ACCESS
关闭的情况下编辑的代码设置,则不会定义行为,因为在FENV_ACCESS
关闭的情况下编译的代码在非默认模式下运行。草案文本不包含这一点,这似乎意味着调用者可以更改模式并调用在FENV_ACCESS
关闭的情况下编译的代码,并且应该定义行为。这意味着在FENV_ACCESS
关闭的情况下编译的代码必须准备好在任何浮点模式下运行。
草案中的同一段也包含以下新案文:
(当执行从用
FENV_ACCESS
"关闭"翻译的程序部分转移到用FENV_ACCESS
"打开"翻译的部分时,浮点状态标志的状态未指定,浮点控制模式具有默认设置。)
考虑当FENV_ACCESS
打开的例程A调用FENV_ACCESS
关闭的例程B时会发生什么。当B返回时,控制从程序的访问关闭部分传递到程序的访问打开部分。上面的句子说浮点控制模式处于默认设置中。换句话说,从访问关闭例程返回必须将浮点模式更改为默认模式。这似乎很奇怪。因此,我不准备更新这个答案来很好地涵盖草案。
C 2018答案
不清楚为什么这个杂注需要在非默认浮点控制模式下运行。
这可能是因为(取决于C实现),如果浮点运算不处于默认模式,编译器生成的代码必须不同。例如,当编译FENV_ACCESS
设置为off
的代码时,编译器可以将对sin
的调用编译为对假定默认舍入的快速版本的调用。但是,如果FENV_ACCESS
设置为on
,它将把调用编译成一个较慢的版本,该版本测试舍入模式并使用正弦函数的相应实现。
因为on
和off
版本必须生成的代码不同,所以编译器必须知道FENV_ACCESS
是on
还是off
。
因此,如果没有指定杂注FENV_ACCESS,是否意味着默认舍入模式有效?
否。如果FENV_ACCESS
杂注不存在,则编译器处于其默认状态,可能是on
或off
,这是实现定义的。
如果默认值是off
,并且没有杂注,那么,是的,默认舍入模式应该有效,这意味着,如果您正确设计了程序,那么任何不使用FENV_ACCESS
杂注编译的代码都不会在非默认舍入模式下执行。这取决于程序设计者来确保。
函数
nearbyint
被描述为(7.12.9.3)使用当前舍入模式进行舍入。但该代码没有pragma FENV_ACCESS
。这是否意味着可以忽略当前舍入模式,并且nearbyint
与roundeven
相同?
如果FENV_ACCESS
设置为off
(默认或显式)的代码调用nearbyint
,则编译器可以假定默认舍入模式有效,并且它可以调用nearbyint
的快速版本,该版本本身假定默认舍入方式。
请注意,四舍五入到最接近的平局是压倒性的默认舍入模式,但除非附录F有效,否则C标准未对此进行规定。