MSVC新版本中NAN不被视为常量的编译问题咨询及兼容方案寻求
MSVC新版本中NAN不被视为常量的编译问题咨询及兼容方案寻求
你遇到的这个问题我之前帮朋友排查过,正好可以给你详细解答:
一、这是MSVC的bug吗?
其实这不算严格意义上的bug,而是MSVC的实现和C标准的细节差异导致的:
- C标准只要求
<math.h>中的NAN宏能表示安静NaN,但并没有强制规定它必须是编译期常量表达式。大多数编译器(比如GCC、Clang)为了方便,把NAN实现成了常量形式,但MSVC这次更新后,把NAN改成了通过__ucrt_int_to_float()函数调用生成的形式——这个函数调用在编译期无法被求值,所以当你用它初始化数组(C语言要求数组的静态初始化器必须是常量表达式)时,就会触发C2099错误。 - 至于
0.0 / 0.0报C2124的问题,是MSVC的编译期静态检查特性:它会在编译时识别出这种明显的除以零操作并报错,而其他编译器通常会把这个表达式留到运行时生成NaN,不会在编译期报错。
二、跨编译器的稳健解决方案
要写出兼容MSVC和其他编译器的代码,核心是自己定义一个能在编译期生成NaN的常量,同时保留对标准NAN宏的兼容。这里有几个靠谱的方案:
方案1:利用浮点类型的二进制表示直接构造NaN
NaN的二进制格式是固定的:
- 对于
float:指数位全1,尾数位非0,十六进制值0x7FC00000是标准的安静NaN - 对于
double:指数位全1,尾数位非0,十六进制值0x7FF8000000000000是标准的安静NaN
我们可以通过类型转换直接构造,用编译宏区分MSVC和其他编译器:
#include <math.h> #ifdef _MSC_VER // MSVC下直接用二进制表示构造常量NaN #define MY_FLOAT_NAN ((float)0x7FC00000U) #define MY_DOUBLE_NAN ((double)0x7FF8000000000000ULL) #else // 其他编译器用标准NAN宏 #define MY_FLOAT_NAN NAN #define MY_DOUBLE_NAN NAN #endif // 使用示例:这样就能正常编译了 double x[] = { 1.0, MY_DOUBLE_NAN };
这个方案的好处是完全在编译期求值,不会触发MSVC的错误,同时在其他编译器下和原来的NAN行为完全一致,兼容性最广。
方案2:用复合字面量(C99及以上版本适用)
如果你的代码只需要兼容C99及更高版本,也可以用联合类型的复合字面量来构造NaN,这种方式更直观:
#include <math.h> #ifdef _MSC_VER typedef union { unsigned int u; float f; } FloatNaN; typedef union { unsigned long long u; double d; } DoubleNaN; #define MY_FLOAT_NAN ((FloatNaN){0x7FC00000U}).f #define MY_DOUBLE_NAN ((DoubleNaN){0x7FF8000000000000ULL}).d #else #define MY_FLOAT_NAN NAN #define MY_DOUBLE_NAN NAN #endif
复合字面量在C99里属于常量表达式,MSVC从VS2015开始支持C99的这个特性,所以只要你的MSVC版本不是太老,这个方案也能正常工作。
方案3:运行时生成(仅适用于非静态初始化场景)
如果你的场景不是必须在编译期初始化(比如函数内部的局部变量赋值),也可以用MSVC提供的运行时函数获取NaN,但这个方法不能用于全局数组或静态数组的初始化:
#include <math.h> #ifdef _MSC_VER #include <float.h> // 调用MSVC提供的_getNaN函数生成NaN #define MY_NAN _getNaN() #else #define MY_NAN NAN #endif
总结
MSVC的这个改变虽然有点反直觉,但并没有违反C标准的严格要求;通过自己构造编译期常量NaN的方式,就能完美解决跨编译器的兼容问题,推荐优先使用方案1,它的兼容性最好,能覆盖绝大多数场景。
备注:内容来源于stack exchange,提问作者Szabolcs
相关产品推荐
相关产品推荐

