我知道未初始化的局部变量是未定义的行为(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域时,任何事情都可能发生(包括守护进程从你鼻子上飞出去),通常意味着编译器作者不会关心,对于局部变量,其值将是当时堆栈内存中的任何值。

这也意味着内容通常是“奇怪的”,但是是固定的,或者是稍微随机的,或者是可变的,但是有一个清晰的模式(例如,在每次迭代中增加值)。

当然,你不能指望它是一个不错的随机生成器。

其他回答

如果操作得当,使用未初始化的数据来获得随机性并不一定是件坏事。事实上,OpenSSL正是这样做的,以播种它的PRNG。

显然,这种用法并没有很好地记录下来,因为有人注意到Valgrind抱怨使用未初始化的数据,并“修复”了它,导致了PRNG中的一个错误。

所以你可以这样做,但你需要知道你在做什么,并确保任何阅读你的代码的人都理解这一点。

正如这里大多数人提到的未定义行为。未定义也意味着你可以得到一些有效的整数值(幸运的是),在这种情况下,这将更快(因为rand函数调用没有进行)。 但不要实际使用它。我相信这将会带来可怕的结果,因为运气并不总是伴随着你。

正如其他人所注意到的,这就是未定义行为(UB)。

实际上,它(可能)实际上(有点)管用。在x86[-64]架构上读取未初始化的寄存器确实会产生垃圾结果,而且可能不会做任何坏事(与例如Itanium相反,那里的寄存器可以被标记为无效,因此读取会传播NaN之类的错误)。

但这里存在两个主要问题:

It won't be particularly random. In this case, you're reading from the stack, so you'll get whatever was there previously. Which might be effectively random, completely structured, the password you entered ten minutes ago, or your grandmother's cookie recipe. It's Bad (capital 'B') practice to let things like this creep into your code. Technically, the compiler could insert reformat_hdd(); every time you read an undefined variable. It won't, but you shouldn't do it anyway. Don't do unsafe things. The fewer exceptions you make, the safer you are from accidental mistakes all the time.

UB更紧迫的问题是它使整个程序的行为没有定义。现代编译器可以使用它来省略大量的代码,甚至可以回溯到过去。玩UB就像维多利亚时代的工程师拆除一个活跃的核反应堆。有无数的事情会出错,而且您可能连一半的基本原则或实现的技术都不知道。这可能没什么,但你仍然不应该让它发生。看看其他漂亮的答案来了解细节。

还有,我会炒了你。

有很多很好的答案,但请允许我补充另一个并强调一点,在确定性计算机中,没有什么是随机的。对于伪rng生成的数字和堆栈上为C/ c++局部变量保留的内存区域中发现的看似“随机”的数字都是如此。

但是…这里有一个关键的区别。

由优秀的伪随机生成器生成的数字具有统计上与真正的随机抽取相似的属性。例如,分布是均匀的。循环长度很长:在循环重复之前,你可以得到数百万个随机数。序列不是自相关的:例如,如果你取第2个、第3个或第27个数字,或者查看生成的数字中的特定数字,你不会开始看到奇怪的模式出现。

相比之下,留在堆栈上的“随机”数字没有任何这些属性。它们的值和明显的随机性完全取决于程序的构造方式、编译方式以及编译器对程序的优化方式。举例来说,这是你的想法的一个变体,作为一个自包含的程序:

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

当我在Linux机器上用GCC编译这段代码并运行它时,结果是相当不愉快的确定性:

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

If you looked at the compiled code with a disassembler, you could reconstruct what was going on, in detail. The first call to notrandom() used an area of the stack that was not used by this program previously; who knows what was in there. But after that call to notrandom(), there is a call to printf() (which the GCC compiler actually optimizes to a call to putchar(), but never mind) and that overwrites the stack. So the next and subsequent times, when notrandom() is called, the stack will contain stale data from the execution of putchar(), and since putchar() is always called with the same arguments, this stale data will always be the same, too.

因此,这种行为绝对不是随机的,通过这种方式获得的数字也不具有编写良好的伪随机数生成器的任何理想属性。事实上,在大多数现实场景中,它们的值是重复的并且高度相关的。

事实上,和其他人一样,我也会认真考虑解雇那些试图把这个想法当作“高性能RNG”的人。

由于安全原因,必须清理分配给程序的新内存,否则信息可能会被使用,密码可能会从一个应用程序泄漏到另一个应用程序。只有在重用内存时,才会得到不同于0的值。很有可能,在堆栈上,前一个值是固定的,因为前一个内存的使用是固定的。