在我们的一个项目中广泛使用的包含文件中,我们有这些便利常量:
const double kPi = asin(1.0) * 2.0;
const double kPiHalf = asin(1.0);
const double kDeg2Rad = asin(1.0) / 90.0;
const double kRad2Deg = 90.0 / asin(1.0);
在运行时使用函数结果计算(精确)常量一次是不是一个坏主意?
最近的一些崩溃转储(在OS X上)我们看起来很狡猾 - 他们有一个堆栈框架,其中const double kRad2Deg
行的地址位于深层,尽管崩溃发生在应用程序的某个地方堆栈跟踪。怪异。
在启动阶段很早就调用数学函数可能搞砸了吗?
我知道我们可以用常量替换函数调用,但我想了解问题(如果有的话)。
答案 0 :(得分:3)
简短回答,请使用<cmath>
。
长答案:在静态初始化程序中调用函数是一个棘手的问题,因为函数本身可能依赖于静态初始化的数据并且未定义初始化的顺序,所以一般,这是不好的做法
Foo.cpp中
namespace {std :: vector <int> data;}
int foo (int i) {
data .push_back (i);
return data .size ();
}
bar.cpp
int f = foo (123);
这很危险,因为data
可能尚未构建。 foo.cpp的安全版本是
namespace {
std :: vector <int> & data () {
static std :: vector <int> d;
return d;
}
}
int foo (int i) {
data () .push_back (i);
return data () .size ();
}
如果你无法保证所涉及的所有功能都遵循这种模式,那么你就不安全了。
另一方面,静态内置类型的值会在ANYTHING发生之前加载到内存中,所以它们没问题。
Foo.cpp中
const double pi = M_PI;
bar.cpp
int main () {
double foo = pi;
}
这很好。
尽管如此,使用<cmath>
,标准要求其值与可能表示的一样准确。执行90.0/asin(1.0)
只会加剧不准确性。
答案 1 :(得分:2)
这实际上不一定是个坏主意......像gcc这样的编译器会使用trig函数,平方根等函数,并使用GMP和MPFR等高端数学库,如果传递给函数的值是常量表达式,则计算结果,并将结果传递给常量表达式。通过使用类似GMP的库来获得结果,而不是使用标准的依赖于平台的libc实现在运行时计算结果,您可以获得更准确的结果作为常数的基础,以及跨平台的一致结果,因为库像GMP / MPRF一样,它可以解决浮点舍入误差等问题,并在多个平台上产生相同的值。
正如所指出的,如果在初始化其他静态对象之前依赖于这些值的初始化,则可能会出现问题。根据C ++ 11规范,第3.6.2节,在动态初始化之前进行常量初始化...所以只要你的表达式有资格进行常量初始化,并且不以某种方式创建循环依赖,那么你应该没问题初始化排序。任何其他非常量初始化的静态对象都将被动态初始化,如果它们依赖于您的常量,那么在动态初始化时这些值就已经初始化了。