未初始化的局部变量是否是最快的随机数生成器?

339

我知道未初始化的本地变量是未定义行为(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);
    }
}

并且比其他随机数生成器更快吗?


95
这是一个完全合理的问题。事实上,在实践中,未初始化的值可能有点随机。它们并不特别,而且这也是未定义行为,并不会因为提出这个问题而变得更糟。 - geometrian
39
@imallett:当然。这是一个好问题,至少在过去的时候,旧的Z80(Amstrad / ZX Spectrum)游戏使用它的程序作为设置地形的数据。因此,这甚至有先例。现在不能这样做了。现代操作系统夺走了所有的乐趣。 - Bathsheba
84
问题的核心在于它并不是随机的。 - john
32
实际上,存在一个未初始化的变量被用作随机值的例子,可以参考Debian RNG灾难(在这篇文章中的示例4)。 - PaperBirdMaster
32
实际上 - 相信我,我在各种架构上都做了很多调试 - 您的解决方案可能会做两件事情:要么读取未初始化的寄存器,要么读取未初始化的内存。虽然“未初始化”在某种程度上意味着随机性,但实际上它很可能包含以下内容:a) _零_,b) _重复或一致的值_(在读取曾经用于数字媒体的内存的情况下),或者c) _具有有限值集的一致垃圾_(在读取曾经用于编码数字数据的内存的情况下)。这些都不是真正的熵源。 - mg30rg
显示剩余24条评论
22个回答

12

就像这里大部分人提到的那样,未定义行为。未定义也意味着你可能会得到一些有效的整数值(幸运的是),在这种情况下,这将更快(因为不需要调用rand函数)。 但实际上不要使用它。我确定这将带来可怕的结果,因为运气并不总是与你同在。


1
非常好的观点!这可能是一个实用的技巧,但确实需要运气。 - meaning-matters
1
这里绝对没有运气的成分。如果编译器不能优化掉未定义行为,那么你得到的值将是完全确定性的(完全取决于你的程序、它的输入、它的编译器、它使用的库以及它的线程的时间安排,如果有线程的话)。问题在于,你无法推理这些值,因为它们取决于实现细节。 - cmaster - reinstate monica
如果没有一个具有与应用程序堆栈分离的中断处理堆栈的操作系统,那么运气可能会介入,因为中断经常会干扰当前堆栈内容之外的内存内容。 - supercat

12

我进行了一个非常简单的测试,而且它根本不是随机的。

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}
每次我运行程序,它都会打印相同的数字(在我的情况下是32767)-- 你不可能比这更不随机了。这很可能是运行时库中启动代码留在堆栈上的内容。由于它每次运行程序都使用相同的启动代码,并且在运行之间没有任何其他变化,因此结果是完全一致的。

好观点。结果强烈取决于代码中调用随机数生成器的位置。它比随机更不可预测。 - Piotr Siupa

12

真糟糕!坏习惯,坏结果。思考:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

如果函数A_Function_that_use_a_lot_the_Stack()总是进行相同的初始化,它会让栈保留相同的数据。这些数据可以通过调用updateEffect()来获得:始终是相同的值!


10

你需要对“随机”有一个明确的定义。 一个合理的定义涉及到所得值应该具有很小的相关性。这是可以测量的。它也不容易以一种可控、可复现的方式实现。因此,未定义的行为肯定不是你要寻找的。


7
在某些情况下,可以使用类型“unsigned char *”安全地读取未初始化的内存(例如从malloc返回的缓冲区)。代码可以读取这样的内存而不必担心编译器会将因果关系抛出窗外,有时准备好处理任何可能包含的内存比确保未初始化数据不被读取更有效率(这种情况的普遍例子是在部分初始化的缓冲区上使用memcpy而不是离散地复制所有包含有意义数据的元素)。
然而,在这种情况下,应始终假设如果任何字节的组合特别棘手,则读取它将总是产生那个字节模式(如果某种模式在生产中很棘手,但在开发中不是,则该模式直到代码处于生产状态才会出现)。
在嵌入式系统中,作为随机生成策略的一部分,读取未初始化的内存可能很有用,其中可以确保自上次系统通电以来,内存从未被写入具有实质性非随机内容,并且如果内存的制造过程导致其上电状态半随机变化。即使所有设备始终产生相同的数据,代码也应该正常工作,但在某些情况下,例如每个节点都需要尽快选择任意唯一ID的情况下,具有“不太随机”的生成器并给一半节点相同的初始ID可能比根本没有任何初始随机源更好。

2
如果任何字节组合都特别令人烦恼,那么读取它将始终产生该字节模式 - 直到您编写代码来处理该模式,此时它就不再令人烦恼,并且将来会读取不同的模式。 - Steve Jessop
@SteveJessop:确切地说,我的关于开发与生产的话是为了传达类似的观点。代码不应该关心未初始化内存中的内容,除了模糊的概念“一些随机性可能很好”。如果程序行为受到一个未初始化内存块的影响,那么将来获取的内存块的内容也可能会受到影响。 - supercat

5
正如其他人所说,它将会很快,但不是随机的。
对于本地变量,大多数编译器会在堆栈上为它们占用一些空间,但不会设置任何值(标准表示它们不需要这样做,那么为什么要减慢你所生成的代码的速度呢?)。
在这种情况下,您将获得的值取决于之前在堆栈上的内容-如果在此函数之前调用了一个具有一百个本地字符变量且全部设置为“Q”的函数,然后在该函数返回后调用您的函数,则您可能会发现您的“随机”值的行为就像您已经使用 memset() 将它们全部设置为“Q”。
对于尝试使用此方法的示例函数非常重要的是,这些值每次读取时都不会改变,每次都相同。因此,您将获得100个将颜色和可见性全部设置为相同的星号。
此外,没有任何规定编译器不应初始化这些值-因此未来的编译器可能会这样做。
总的来说:不好的想法,不要这样做。 (就像许多“聪明”的代码级优化一样……)

2
尽管由于未定义行为(UB)而没有任何保证,但您正在对将要发生的事情做出一些强烈的预测。在实践中也不是真的。 - usr

3

我喜欢你的思维方式。确实非常独到。然而,这种权衡真的不值得。 内存-运行时间平衡是一种存在,在运行时出现未定义的行为是不可取的

知道你正在使用这样的“随机数”作为你的业务逻辑肯定会让你感到非常不安。我不会这样做。


3

正如其他人已经提到的,这是未定义行为(UB),但它可能“运行”。

除了其他人已经提到的问题外,我看到另一个问题(缺点) - 它将无法在除C和C++之外的任何语言中工作。我知道这个问题是关于C++的,但如果你能编写既是良好的C++代码又是Java代码,并且没有问题,那么为什么不呢?也许有一天会有人需要将其移植到其他语言,搜索由于像这样的“魔术技巧” UB引起的bug绝对是一场噩梦(特别是对于经验不足的C/C++开发人员)。

这里有一个关于另一个类似UB的问题。想象一下自己试图找到这样的错误而不知道这个UB。如果您想阅读有关C/C++中这种奇怪事情的更多信息,请阅读链接问题的答案并查看这个精彩幻灯片演示文稿。它将帮助您了解底层原理和工作方式;它不仅是另一个充满“魔术”的幻灯片演示文稿。我相当确定,即使是大多数经验丰富的C/C++程序员也可以从中学到很多东西。


3

在你想使用未初始化变量的任何地方,请使用7757。我从质数列表中随机选择了它:

  1. 这是定义行为

  2. 它保证不总是为0

  3. 它是质数

  4. 与未初始化变量一样,它很可能是统计上随机的

  5. 由于其值在编译时已知,因此它很可能比未初始化变量更快


请参考此答案中的结果进行比较:https://dev59.com/alwZ5IYBdhLWcg3wLd2z#31836461 - Glenn Teitelbaum

3
不要依赖于语言未定义的行为来构建逻辑,这不是一个好主意。除了本帖中提到和讨论的内容之外,我还想提到,采用现代C++的方法/风格编写的程序可能无法编译通过。
在我的先前帖子中提到了这一点,其中包含了使用auto特性的优势和相关的有用链接。

https://dev59.com/8mw15IYBdhLWcg3wu-E9#26170069

因此,如果我们更改上述代码并使用auto替换实际类型,程序甚至无法编译。

void updateEffect(){
    for(int i=0;i<1000;i++){
        auto r;
        auto g;
        auto b;
        star[i].setColor(r%255,g%255,b%255);
        auto isVisible;
        star[i].setVisible(isVisible);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接