POO*_*PTA 14 c++ arrays static
我在C++中遇到了一个有趣的安全编码规则:
在初始化静态变量声明期间不要重新输入函数.如果在该函数内的静态对象的常量初始化期间重新输入函数,则程序的行为是未定义的.触发未定义的行为不需要无限递归,该函数只需要在初始化时重复一次.
不合规的例子是:
#include <stdexcept>
int fact(int i) noexcept(false) {
if (i < 0) {
// Negative factorials are undefined.
throw std::domain_error("i must be >= 0");
}
static const int cache[] = {
fact(0), fact(1), fact(2), fact(3), fact(4), fact(5),
fact(6), fact(7), fact(8), fact(9), fact(10), fact(11),
fact(12), fact(13), fact(14), fact(15), fact(16)
};
if (i < (sizeof(cache) / sizeof(int))) {
return cache[i];
}
return i > 0 ? i * fact(i - 1) : 1;
}
Run Code Online (Sandbox Code Playgroud)
根据来源给出错误:
terminate called after throwing an instance of '__gnu_cxx::recursive_init_error'
what(): std::exception
Run Code Online (Sandbox Code Playgroud)
在Visual Studio 2013中执行时.我尝试了类似的自己的代码并得到了相同的错误(使用g ++编译并在Ubuntu上执行).
如果我的理解对于这个概念是正确的,我很怀疑,因为我不熟悉C++.据我所知,由于缓存数组是常量的,这意味着它可以是只读的,只需要初始化一次作为静态,它会一次又一次地初始化,因为这个数组的值是每个返回的值.逗号分隔的递归函数调用,它违反了声明的数组的行为.因此,它给出了未定义的行为,这也在规则中说明.
对此更好的解释是什么?
Bar*_*rry 17
要执行fact(),您需要先静态初始化fact::cache[].最初fact::cache,您需要执行fact().那里有一个循环依赖,这导致你看到的行为.cache只会被初始化一次,但它需要自己初始化才能初始化自己.即使输入这个也让我头晕目眩.
引入这样的缓存表的正确方法是将其分成不同的函数:
int fact(int i) noexcept(false) {
if (i < 0) {
// Negative factorials are undefined.
throw std::domain_error("i must be >= 0");
}
return i > 0 ? i * fact(i - 1) : 1;
}
int memo_fact(int i) noexcept(false) {
static const int cache[] = {
fact(0), fact(1), fact(2), fact(3), fact(4), fact(5),
fact(6), fact(7), fact(8), fact(9), fact(10), fact(11),
fact(12), fact(13), fact(14), fact(15), fact(16)
};
if (i < (sizeof(cache) / sizeof(int))) {
return cache[i];
}
else {
return fact(i);
}
}
Run Code Online (Sandbox Code Playgroud)
这里memo_fact::cache[]只会初始化一次 - 但它的初始化不再依赖于它自己.所以我们没有问题.
C++标准§6.7/ 4说明了关于具有静态存储持续时间的块范围变量的初始化:
如果控件在初始化变量时以递归方式重新输入声明,则行为未定义.
以下信息示例如下:
Run Code Online (Sandbox Code Playgroud)int foo(int i) { static int s = foo(2*i); // recursive call - undefined return i+1; }
这也适用于您的示例.fact(0)是递归调用,因此cache重新输入声明.调用未定义的行为.
重要的是回忆一下未定义的行为意味着什么.未定义的行为意味着一切都可能发生,而"一切"很自然地包括被抛出的异常.
未定义的行为还意味着您不能再对代码中的任何其他内容进行推理,除非您真的想要了解编译器实现的详细信息.但是,在使用编程语言方面,你不再谈论C++,而是在如何实现该语言方面.