在C语言程序中,未初始化的局部变量值是未定义(UB,undefined behaviour)的,使用未初始化的变量可能会为程序带来意想不到的错误,这一点看过我文章的读者应该都是清楚的。
但是,反过来想一想,“未定义的”也就意味着未初始化的局部变量内部的值是不确定的,那么它能不能被当作“随机值”使用呢?例如下面这段C语言代码:
void updateEffect(){
for(int i=0;i<1000;i++){
int r, g, b;
star[i].setColor(r%255,g%255,b%255);
bool isVisible;
star[i].setVisible(isVisible);
}
}
程序仅仅把未初始化的局部变量 r, g, b, isVisible 当作随机值使用,并未在其他逻辑中使用,这样做看起来要比调用 rand() 函数产生随机值效率高多了:
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);
}
}
真的如此吗?
首先应该明白,计算机语言是绝对不能有歧义的,因此为C语言程序引入“未定义”的行为无疑是有违这一原则的。另外,使用未初始化局部变量作为随机值,不太可能具有理想的数学统计特性。
例如在 x86(_ 64) 架构中,从未初始化的寄存器中确实会读取出不能事先预知的值,但是这样的值并不能当作是“随机值”,因为局部变量的值存储在栈中,未初始化的值其实是栈中对应位置上一次使用者留下的。
所以如果该栈区一直没有被使用过,那么即使未初始化的局部变量也会每次都有相同的值,这显然不符合预期——产生“随机值”。换句话说,希望未初始化的局部变量提供“随机值”实在是不可靠,甚至指望它每次提供不一样的值都需要依靠运气。
即使在某段C语言代码中,未初始化的局部变量能够每次都提供不同的值,它们的数学统计特性也是不可靠的,我们不能指望它符合某种分布。
与此同时,引入“未定义”的行为到我们的C语言代码中永远是不值得推荐的做法。程序员永远都不该写不安全的代码,引入的例外越少,最终的程序就越容易避免意外错误。
因此,在C语言程序开发中,如果希望得到一个随机数,那么通过 rand() 函数获取就是最好的选择。基本上也不用担心它的效率,因为基于 rand() 函数的随机数生成器只不过是一个乘法运算,一个加法运算和一个模运算而已。
我所知道的最快的生成器是使用 uint32_t 类型作为随机变量,并且通过下面这个表达式计算:
I = 1664525 * I + 1013904223;
I 的初始值即为所谓的随机数种子,这个表达式可以产生连续的随机数。为了尽量得到高效率的随机数生成器,内联函数和函数式宏定义都是不错的选择。
上面表达式里的两个常数,是由著名的科学程序员 Donald Knuth 手工挑选的。
小结
事实上,随机性是程序开发中非常具体,又非常难以获得的属性。把一些难以追踪的(未定义)数值当作随机值,是一种常见的错误。