未初始化的局部变量是最快的随机数生成器吗?
我知道未初始化的局部变量是未定义的行为(UB),并且该值可能具有可能影响进一步操作的陷阱表示,但有时我想仅将该随机数用于视觉表示,并且不会在其他部分进一步使用它们程序,例如,用随机颜色设置视觉效果,例如:
void updateEffect(){
for(int i=0;i<1000;i++){
int r;
int g;
int b;
star[i].setColor(r%255,g%255,b%255);
bool isVisible;
star[i].setVisible(isVisible);
}
}
比它快吗?
void updateEffect(){
for(int i=0;i<1000;i++){
star[i].setColor(rand()%255,rand()%255,rand()%255);
star[i].setVisible(rand()%2==0?true:false);
}
}
也比其他随机数生成器更快?
正如其他人所指出的,这是未定义的行为(UB)。
在实践中,它可能(可能)实际上(种类)的工作。 从x86 [-64]体系结构上的未初始化寄存器中读取确实会产生垃圾结果,并且可能不会做任何坏事(与Itanium相反,例如Itanium,寄存器可能被标记为无效,以致读取传播错误,如NaN)。
但有两个主要问题:
它不会特别随机。 在这种情况下,你正在阅读堆栈,所以你会得到以前的任何东西。 这可能是有效的随机,完全结构化,您在十分钟前输入的密码或您祖母的Cookie配方。
这是坏的(大写'B')的做法,让这样的事情蔓延到你的代码中。 从技术上讲,编译器可以插入reformat_hdd();
每次你读一个未定义的变量。 它不会,但你不应该这样做。 不要做不安全的事情。 您制造的例外越少,您始终都会因意外错误而更加安全。
UB更紧迫的问题是它会使整个程序的行为不确定。 现代编译器可以使用它来缩短代码的大部分时间,甚至可以及时回溯。 和UB一起玩就像维修工程师拆解活的核反应堆。 有太多的事情会出错,你可能不会知道一半的基本原理或实施的技术。 这可能是好的,但你仍然不应该让它发生。 细节请看其他好的答案。
另外,我会解雇你。
让我清楚地说: 我们不会在我们的程序中调用未定义的行为 。 这永远不是一个好主意,一段时间。 这条规则极少有例外; 例如,如果你是一个实现offsetof的库实现者。 如果你的情况属于这种例外,你可能已经知道这一点。 在这种情况下,我们知道使用未初始化的自动变量是未定义的行为。
编译器对未定义行为进行优化变得非常积极,我们可以发现许多未定义行为导致安全缺陷的情况。 最臭名昭着的情况可能是Linux内核空指针检查删除,我在我的答案中提到的C ++编译错误? 围绕未定义行为的编译器优化将有限循环转变为无限循环。
我们可以阅读CERT的危险优化和损失因果关系,其中包括:
编译器编写者越来越多地利用C和C ++编程语言中未定义的行为来改进优化。
通常,这些优化会干扰开发人员对其源代码执行因果分析的能力,即分析下游结果对先前结果的依赖性。
因此,这些优化消除了软件中的因果关系,并增加了软件故障,缺陷和漏洞的可能性。
特别是对于不确定值,C标准缺陷报告451:未初始化的自动变量的不稳定性使得一些有趣的读数成为可能。 它尚未解决,但引入了摇摆价值观的概念,这意味着价值的不确定性可能通过该计划传播,并且在计划的不同点可能具有不同的不确定值。
我不知道发生这种情况的任何例子,但在这一点上我们不能排除。
真实的例子,而不是你期望的结果
你不可能得到随机值。 一个编译器可以优化完全离开循环。 例如,在这个简化的情况下:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r ;
}
}
铿锵优化它(看到它住):
updateEffect(int*): # @updateEffect(int*)
retq
或者可能获得全零,如同这个修改后的情况一样:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r%255 ;
}
}
现场观看:
updateEffect(int*): # @updateEffect(int*)
xorps %xmm0, %xmm0
movups %xmm0, 64(%rdi)
movups %xmm0, 48(%rdi)
movups %xmm0, 32(%rdi)
movups %xmm0, 16(%rdi)
movups %xmm0, (%rdi)
retq
这两种情况都是完全可以接受的未定义行为形式。
请注意,如果我们在安腾上,我们可能会得到一个陷阱值:
[...]如果寄存器碰巧存有特殊的非物件值,读取寄存器陷阱除了几个指令[...]
其他重要说明
有趣的是,注意到UB Canaries项目中gcc和clang之间的差异,他们是否愿意利用关于未初始化内存的未定义行为。 文章指出(重点是我的):
当然,我们需要完全明白,任何这样的期望都与语言标准无关,并且与编译器碰巧做什么无关,要么是因为编译器的提供者不愿意利用该UB ,因为他们还没有到处利用它 。 当编译器提供者没有真正的保证时, 我们想说尚未开发的UB是时间炸弹 :它们等待下个月或明年编译器变得更具攻击性。
正如Matthieu M.指出,每个C程序员应该知道的未定义行为#2/3也与这个问题有关。 它说除其他外(重点是我的):
要认识到的重要和可怕的事情是, 几乎任何基于未定义行为的优化都可能会在未来的任何时候在错误的代码中触发 。 内联,循环展开,内存升级和其他优化将继续变得越来越好,其存在的重要部分原因是揭示次优化如上所述。
对我而言,这是深深的不满,部分原因是编译器不可避免地最终被指责,但也因为这意味着庞大的C代码体系就是等待爆炸的地雷。
为了完整起见,我应该提到实现可以选择使未定义的行为明确定义,例如gcc允许在C ++中通过联合对类型进行窜改,这似乎是未定义的行为。 如果是这种情况,实施应该记录下来,而且这通常不是可移植的。
不,这太可怕了。
在C和C ++中都未定义使用未初始化变量的行为,并且这种方案不太可能具有理想的统计属性。
如果你想要一个“快速和肮脏”的随机数发生器,那么rand()
是你最好的选择。 在它的实现中,它所做的只是一个乘法,一个加法和一个模数。
我所知道的最快的生成器要求您使用uint32_t
作为伪随机变量I
的类型,然后使用
I = 1664525 * I + 1013904223
生成连续的值。 你可以选择任何初始值( I
称之为种子),这个初始值就是你喜欢的。 显然你可以用内联代码。 无符号类型的标准保证环绕作为模数。 (这个数字常量是由那个卓越的科学程序员Donald Knuth精心挑选的。)
上一篇: Is uninitialized local variable the fastest random number generator?
下一篇: Why arm