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


329

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

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


88
+1这是一个完全合理的问题。实际上,未初始化的值可能是随机的。他们不特别,它的UB没有任何事实那么糟糕。
imallett

35
@imallett:绝对。这是一个很好的问题,过去至少有一个旧的Z80(Amstrad / ZX Spectrum)游戏使用其程序作为数据来设置其地形。因此,甚至有先例。这些天不能这样做。现代操作系统带走了所有的乐趣。
Bathsheba

81
当然,主要的问题是它不是随机的。
约翰

30
实际上,有一个未初始化的变量用作随机值的示例,请参见Debian RNG灾难本文的示例4 )。
PaperBirdMaster

31
在实践中-相信我,我在各种体系结构上进行了大量调试-您的解决方案可以做两件事:读取未初始化的寄存器或未初始化的内存。现在,虽然“未初始化”以某种方式表示随机,但实际上它很可能包含a) b) 重复或一致的值(在读取以前由数字媒体占用的内存的情况下)或c) 有限值的一致的垃圾设置(在读取以前由编码的数字数据占用的内存的情况下)。这些都不是真正的熵源。
mg30rg

Answers:


299

正如其他人指出的那样,这是未定义行为(UB)。

在实践中,它将(可能)实际(某种)起作用。在x86 [-64]架构上从未初始化的寄存器读取数据确实会产生垃圾结果,并且可能不会做任何坏事(例如与Itanium相反,在Itanium中,寄存器可以标记为invalid,从而读取传播错误,例如NaN)。

但是,存在两个主要问题:

  1. 它不会特别随机。 在这种情况下,您正在从堆栈中读取数据,因此您将获得之前的所有内容。这可能是随机的,完全结构化的,您十分钟前输入的密码或祖母的cookie配方。

  2. 让类似这样的内容渗入您的代码是不好的做法(大写的“ B”)。从技术上讲,编译器可以在reformat_hdd();您每次读取未定义的变量时插入。它不会,但你不应该这样做也无妨。不要做不安全的事情。您所做的例外越少,始终可以避免意外错误的发生。

UB更为紧迫的问题是,它使整个程序的行为不确定。现代的编译器可以使用它消除大量的代码,甚至可以追溯到过去。和UB一起玩就像是维多利亚时代的工程师拆除了一个活核反应堆。有成千上万的事情要出错,而且您可能不知道一半的基本原理或已实现的技术。这可能是好的,但你还是不应该让这件事发生。查看其他不错的答案以获取详细信息。

而且,我会解雇你。


39
@Potatoswatter:Itanium寄存器可以包含NaT(不是事物),实际上是“未初始化的寄存器”。在Itanium上,如果未写入寄存器就从寄存器中读取可能会中止程序(在此处了解更多信息:blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx)。因此,有充分的理由解释为什么读取未初始化的值是未定义的行为。这也可能是Itanium不太受欢迎的原因之一:)
tbleher

58
我真的反对“这种作品”的概念。即使今天是对的(事实并非如此),由于更激进的编译器,它可能随时更改。编译器可以用替换任何读取的内容,unreachable()并删除程序的一半。在实践中也确实如此。我相信这种行为确实完全抵消了某些Linux发行版中的RNG。这个问题的大多数答案似乎都假设未初始化的值的行为就像一个值。错了
usr

25
另外,我想请您说一句相当愚蠢的话,假设好的做法应该在代码审阅,讨论和避免再次发生的时候进行。因为我们使用的是正确的警告标志,所以绝对应该抓住这一点,对吧?
沙菲克·雅格慕

17
@Michael其实是。如果程序在任何时候都具有未定义的行为,则编译器可以以一种会影响调用未定义行为之前的代码的方式来优化您的程序。关于如何令人费解的想法,有很多文章和演示。这是一个很好的例子:blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx(其中包括标准中的如果您程序中的任何路径调用UB,所有投注都将取消)
Tom Tanner

19
这个答案听起来好像“在理论上调用未定义的行为是不好的,但实际上并不会对您造成太大的伤害”。错了 从表情收集熵会导致UB (也许)导致丢失所有先前收集的熵。这是一个严重的危险。
Theodoros Chatzigiannakis

213

让我清楚地说一下:我们不会在程序中调用未定义的行为。这段时间从来都不是一个好主意。该规则很少有例外。例如,如果您是实现offsetof库实现者。如果您的案件属于此类例外,您可能已经知道这一点。在这种情况下,我们知道使用未初始化的自动变量是未定义的行为

编译器在围绕未定义行为的优化方面变得非常积极,我们可以发现许多情况下未定义行为导致安全漏洞。最臭名昭著的情况可能是我在回答C ++编译错误时提到的Linux内核空指针检查删除围绕未定义行为的编译器优化将有限循环变成了无限循环。

我们可以阅读CERT的危险优化和因果关系丧失视频),其中包括:

编译器编写者越来越多地利用C和C ++编程语言中的未定义行为来改善优化。

通常,这些优化会干扰开发人员对其源代码执行因果分析的能力,即分析下游结果对先前结果的依赖性。

因此,这些优化消除了软件中的因果关系,并增加了软件故障,缺陷和漏洞的可能性。

特别是对于不确定值,C标准缺陷报告451:未初始化的自动变量的不稳定性引起了一些有趣的阅读。它尚未解决,但引入了摆动值的概念,这意味着的不确定性可能会在程序中传播,并且在程序中的不同点可能具有不同的不确定值。

我不知道发生这种情况的任何示例,但目前我们不能排除它。

真实的例子,而不是您期望的结果

您不太可能获得随机值。编译器可以完全优化循环。例如,使用这种简化的情况:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r ;
    }
}

clang对其进行了优化(现场观看):

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

这两种情况都是未定义行为的完全可接受的形式。

注意,如果我们使用的是Itanium,则可能会得到一个trap值

[...]如果寄存器碰巧持有一个特殊的“无”值,则除几条指令外,读取寄存器陷阱[...]

其他重要说明

有趣的是,UB Canaries项目中提到的gcc和clang之间差异在于,它们如何愿意就未初始化的内存利用未定义的行为。本文注释(重点是我的):

当然,我们需要完全清楚自己,任何这种期望都与语言标准无关,并且与特定编译器发生的事情无关,这要么是因为该编译器的提供者不愿意利用该UB,要么仅仅是因为他们还没有开始开发它。当没有编译器提供者的真正保证时,我们要说尚未被利用的UB是定时炸弹:当编译器变得更具攻击性时,它们正等待下个月或明年开始使用。

正如Matthieu M.指出的,每个C程序员应该了解的未定义行为#2/3也与此问题相关。它说(尤其是我的):

要意识到的重要和可怕的事情是, 将来任何时候几乎所有基于未定义行为的优化都可以在错误代码上开始触发。内联,循环展开,内存提升和其他优化将不断改善,它们存在的重要原因之一就是要公开上述的次级优化。

对我来说,这是非常不满意的,部分原因是编译器不可避免地会受到指责,还因为这意味着大量的C代码主体只是等待爆炸的地雷。

为了完整起见,我可能应该提到实现可以选择使未定义行为明确定义,例如,gcc允许通过联合进行类型修剪,在C ++中,这似乎是未定义行为。如果是这种情况,则实现应将其记录下来,并且通常不能移植。


1
+(int)(PI / 3)用于编译器输出示例;现实生活中的例子,UB是,好,UB

2
有效地使用UB曾经是优秀黑客的商标。这种传统可能已经持续了50年以上。不幸的是,现在由于坏人,现在需要计算机以最大程度地降低UB的影响。我真的很喜欢弄清楚如何使用UB机器代码或端口读/写等来完成一些酷的事情,例如在90年代,当时操作系统还不能保护用户免受自身攻击。
sfdcfox

1
@sfdcfox,如果您是在机器代码/汇编器中执行的,则它不是未定义的行为(它可能是非常规的行为)。
卡雷斯(Caleth)

2
如果您有特定的程序集,请使用该程序集,而不要编写不兼容的C。然后每个人都会知道您正在使用特定的非便携式技巧。不是坏人意味着您不能使用UB,而是Intel等人在芯片上发挥作用。
卡雷斯(Caleth)

2
@ 500-InternalServerError,因为在一般情况下它们可能不容易被检测到或根本无法被检测到,因此将没有办法禁止它们。不同于可以检测到的语法违规。我们也没有格式错误或格式错误的诊断程序,通常不需要将诊断上较差的程序从理论上可以可靠地检测出的程序中分离出来。
沙菲克·雅格慕

164

不,太可怕了

在C和C ++中,都未定义使用未初始化变量的行为,并且这种方案不太可能具有理想的统计属性。

如果您想要一个“快速而肮脏的”随机数生成器,那么rand()最好的选择是。在其实现中,它所做的只是一个乘法,一个加法和一个模数。

我所知道的最快的生成器要求您使用a uint32_t作为伪随机变量的类型I,并使用

I = 1664525 * I + 1013904223

生成连续值。您可以选择任何花哨的I(称为seed)初始值。显然,您可以内联编码。无符号类型的标准保证的环绕式充当模数。(数字常量是由杰出的科学程序员唐纳德·克努斯(Donald Knuth)亲自挑选的。)


9
您提供的“线性同余”生成器适用于简单的应用程序,但仅适用于非加密应用程序。可以预测其行为。例如,请参见Don Knuth自己的“ 解密线性同余加密 ”(IEEE Transactions on Information Theory,第31卷)
Jay

24
@Jay与快速和肮脏的统一变量相比?这是一个更好的解决方案。
Mike McMahon 2015年

2
rand()我认为这不适合目的,应该完全弃用。如今,您可以下载免费许可且性能卓越的随机数生成器(例如Mersenne Twister),它们的运行速度几乎与最大轻松一样,因此实际上无需继续使用严重缺陷的设备rand()
Jack Aidley

1
rand()还有另一个可怕的问题:它使用了一种称为内部线程的锁,它极大地降低了代码的速度。至少有一个可重入版本。而且,如果您使用C ++ 11,则随机API将提供您所需的一切。
Marwan Burelle

4
公平地说,他没有问这是否是一个好的随机数生成器。他问是否很快。好吧,是的,它可能是禁食的。,但是结果根本不会是非常随机的。
jcoder 2015年

42

好问题!

未定义并不意味着它是随机的。考虑一下,您在全局未初始化变量中获得的值由系统或您运行的其他应用程序保留在那里。根据系统在不再使用内存的情况下所做的事情和/或系统和应用程序生成什么样的值,您可能会得到:

  1. 总是一样。
  2. 成为一小部分价值观中的一个。
  3. 获取一个或多个小范围内的值。
  4. 在16/32/64位系统上的指针中可以看到许多可被2/4/8划分的值
  5. ...

您将获得的值完全取决于系统和/或应用程序留下了哪些非随机值。因此,确实会有一些噪音(除非您的系统擦除不再使用的内存),但是您从中提取的价值池绝不会是随机的。

对于局部变量,情况变得更加糟糕,因为它们直接来自您自己程序的堆栈。您的程序很有可能在执行其他代码期间实际写入这些堆栈位置。我估计在这种情况下运气的机会非常低,而您进行的“随机”代码更改会尝试这种运气。

了解随机性。如您所见,随机性是非常具体且难以获得的属性。一个普遍的错误是认为,如果您仅采取一些难以跟踪的事情(如您的建议),您将获得一个随机值。


7
...而这遗漏了所有编译器优化,它们将完全破坏该代码。
Deduplicator 2015年

6 ...在调试和发行版中,您将获得不同的“随机性”。未定义表示您做错了。
Sql Surfer

对。我会用“未定义”!=“任意”!=“随机”来缩写或总结。所有这些“未知”都具有不同的属性。
6

保证全局变量具有定义的值,无论是否显式初始化。 这是C ++绝对真实,并用C,以及
布莱恩·范登堡

32

许多好的答案,但让我补充一点,并强调一点,在确定性计算机中,没有什么是随机的。对于由伪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

如果您使用反汇编器查看编译后的代码,则可以详细重建正在发生的事情。对notrandom()的第一次调用使用了该程序先前未使用的堆栈区域;谁知道里面有什么。但是在对notrandom()的调用之后,有一个对printf()的调用(GCC编译器实际上将其优化为对putchar()的调用,但没关系),并且该操作覆盖了堆栈。因此,下一次及以后的时间,当调用notrandom()时,堆栈将包含来自执行putchar()的陈旧数据,并且由于总是使用相同的参数调用putchar(),因此该陈旧数据将始终相同,太。

因此,这种行为绝对没有任何随机性,以这种方式获得的数字也不具有编写良好的伪随机数生成器的任何理想属性。实际上,在大多数现实情况下,它们的价值将是重复的且高度相关。

的确,和其他人一样,我也将认真考虑解雇试图将这种想法称为“高性能RNG”的人。


1
“在确定性计算机中,没有什么是随机的” —实际上并非如此。现代计算机包含各种传感器,使您无需单独的硬件生成器即可产生真正的,不可预测的随机性。在现代体系结构上,的值/dev/random通常是从此类硬件来源中获得的,实际上是“量子噪声”,即,从单词的最佳物理意义上来说,这确实是不可预测的。
康拉德·鲁道夫

2
但是,那不是确定性计算机,是吗?您现在依靠环境输入。无论如何,这使我们远远超出了对未初始化存储器中常规伪RNG与“随机”位的讨论。另外...查看/ dev / random的描述,以了解实现者为确保随机数在密码学上的安全而付出的努力...正是因为输入源不是纯净的,不相关的量子噪声,而是而是可能具有高度相关性的传感器读数,并且随机程度很小。这也很慢。
维克托·托特

29

未定义的行为意味着编译器的作者可以自由地忽略该问题,因为程序员将永远无权抱怨发生的任何事情。

从理论上讲,进入UB土地时会发生任何事情(包括守护程序从你的鼻子上飞走),通常意味着编译器作者根本不在乎,对于局部变量,该值将是此时堆栈内存中的值。

这也意味着内容经常是“奇怪的”,但是固定的,或者是稍微随机的或可变的,但是具有明显的明显模式(例如,每次迭代时增加值)。

当然,您不能期望它是一个像样的随机发生器。


28

未定义的行为是未定义的。这并不意味着您获得了一个未定义的值,而是意味着该程序可以执行任何操作并且仍然符合语言规范。

一个好的优化编译器应该

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);
    }
}

并将其编译为noop。这肯定比任何替代方法都快。它的缺点是它不会做任何事情,但这就是不确定行为的缺点。


3
在很大程度上取决于编译器的目的是帮助程序员生成满足域要求的可执行文件,还是目的是生成性能最高的“高效”可执行文件,其行为与C标准的最低要求一致,而无需考虑这种行为是否会有用。关于前一个目标,让代码对r,g,b使用一些任意的初始值,或者在可行的情况下触发调试器陷阱,比将代码转换为nop更为有用。关于后一个目标...
超级猫

2
...最佳的编译器应确定哪些输入将导致上述方法执行,并消除仅在接收到此类输入时才相关的任何代码。
2015年

1
@supercat或它的目的可能是C.生成符合标准的高效可执行文件,同时帮助程序员找到合规性可能没有用的地方。编译器可以发出比标准要求更多的诊断信息(例如GCC的诊断信息)来满足这种折衷的目的-Wall -Wextra
Damian Yerrick

1
值未定义并不表示周围代码的行为未定义。任何编译器都不应使用该函数。绝对必须调用这两个函数调用,无论输入是什么。第一个必须使用0到255之间的三个数字来调用,第二个必须使用true或false值来调用。一个“好的优化编译器”可以将函数参数优化为任意静态值,从而完全摆脱变量,但是要尽其所能(当然,除非函数本身可以在某些输入上减少为noops)。
Dewi Morgan

@DewiMorgan-由于所调用的函数属于“设置此参数”类型,因此,几乎可以肯定的是,当输入与参数的当前值相同时,它们确实会减少为无操作,编译器可以自由地假设是这种情况。
2015年

18

尚未提及,但是允许调用未定义行为的代码路径执行编译器想要执行的任何操作,例如

void updateEffect(){}

当然,这比正确的循环要快,而且由于使用了UB,因此完全符合要求。


18

由于安全原因,必须清除分配给程序的新内存,否则可能会使用该信息,并且密码可能会从一个应用程序泄漏到另一个应用程序。仅当您重用内存时,您获得的值才不同于0。而且很有可能堆栈中的先前值只是固定的,因为该内存的先前使用是固定的。


13

您的特定代码示例可能不会达到您的期望。从技术上讲,循环的每次迭代都会为r,g和b值重新创建局部变量,但实际上,它是堆栈上完全相同的内存空间。因此,它不会随着每次迭代而重新随机化,并且最终您将为1000种颜色中的每一种分配相同的3个值,而不管r,g和b在初始时的随机性如何。

确实,如果它确实起作用了,我会对重新对其进行随机化感到很好奇。我唯一能想到的就是一个交错的中断,它会piggy绕在栈顶,这极不可能。也许将这些保留为寄存器变量而不是真正的内存位置(在寄存器中循环再使用这些内存)的内部优化也可以解决问题,尤其是在设置可见性函数特别耗费寄存器的情况下。尽管如此,远非随机。


12

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


1
很好点!这可能是一个务实的把戏,但确实需要运气。
含义-2015年

1
绝对没有运气。如果编译器没有优化未定义的行为,那么您获得的值将是完全确定的(=完全取决于程序,其输入,其编译器,所使用的库,具有线程的线程时间)。问题在于您无法推理这些值,因为它们取决于实现细节。
cmaster-恢复莫妮卡2015年

在没有与应用程序堆栈分离的中断处理堆栈的操作系统的情况下,很可能会遇到运气,因为中断经常会干扰内存的内容,使其略微超出当前堆栈的内容。
2015年

12

特别糟糕!坏习惯,不好的结果。考虑:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

如果函数A_Function_that_use_a_lot_the_Stack()总是进行相同的初始化,则它将在堆栈上保留相同的数据。这些数据就是我们所要调用的updateEffect()总是相同的值!


11

我执行了一个非常简单的测试,并且它并不是完全随机的。

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

每次我运行程序时,它都会打印相同的数字(32767就我而言)-您获得的随机性不会比这少得多。大概是堆栈上剩下的运行时库中的启动代码。由于它每次运行程序时都使用相同的启动代码,并且两次运行之间的程序没有其他变化,因此结果是完全一致的。


好点子。结果在很大程度上取决于该“随机”数字生成器在代码中的调用位置。它是不可预测的,而不是随机的。
NO_NAME 2015年

10

您需要定义“随机”的含义。明智的定义是,您获得的值之间应该没有多少相关性。那是您可以测量的。以可控,可重复的方式实现目标也不是一件容易的事。因此,不确定的行为当然不是您想要的。


7

在某些情况下,可以使用类型“ unsigned char *”安全地读取未初始化的内存(例如,从返回的缓冲区malloc)。代码可以读取这样的内存,而不必担心编译器会将因果关系抛出窗口。有时,为可能包含的任何内存准备代码要比确保不读取未初始化的数据更有效(一个常见的例子是memcpy在部分初始化的缓冲区上使用,而不是离散地复制包含有意义数据的所有元素。

但是,即使在这种情况下,也应该始终假设,如果字节的任何组合特别令人生厌,则读取它总是会产生该字节模式(并且如果某种特定的模式在生产中而不在开发中会令人生厌,那么直到代码投入生产,才会显示此模式)。

在嵌入式系统中,读取未初始化的内存可能是随机生成策略的一部分,该策略可以确保自上次开机后以及制造过程中从未写入过基本上非随机的内容。用于内存的进程导致其加电状态以半随机方式变化。即使所有设备总是产生相同的数据,代码也应该起作用,但是在例如一组节点每个都需要尽快选择任意唯一ID的情况下,使用“非随机性”生成器为一半节点提供相同的初始值。 ID可能比根本没有任何初始随机性要好。


2
“如果字节的任何组合特别令人生厌,读取它总是会产生该字节模式” –直到您编写代码以应对该模式为止,此时不再不再令人烦恼,并且将来会读取其他模式。
史蒂夫·杰索普

@SteveJessop:准确地说。我关于开发与生产的观点旨在传达类似的观点。除了模糊的“有些随机性可能很好”的概念外,代码不应该关心未初始化的内存中的内容。如果程序行为受一个未初始化的存储器的内容影响,那么将来获取的存储器的内容可能会因此受到影响。
2015年

5

正如其他人所说,这将是快速的,但不是随机的。

大多数编译器将对局部变量进行的操作是在堆栈上为它们获取一些空间,而不用将其设置为任何值(标准说它们不需要这样做,那么为什么要减慢正在生成的代码的速度?)。

在这种情况下,您将获得的值将取决于之前在堆栈上的内容-如果您在此函数之前调用了一个函数,该函数的一百个本地char变量都设置为“ Q”,然后在之后调用您的函数返回值,那么您可能会发现“随机”值的行为就好像memset()它们都属于“ Q”一样。

重要的是,对于尝试使用此功能的示例函数,每次读取它们时这些值都不会改变,它们每次都相同。因此,您将获得100颗均设置为相同颜色和可见度的星星。

同样,也没有什么说编译器不应该初始化这些值的,因此将来的编译器可能会这样做。

一般来说:不好的主意,不要这样做。(就像很多“聪明的”代码级优化一样……)


2
您对即将发生的事情做出了一些有力的预测,尽管由于UB而无法保证。在实践中也不是真的。
usr

3

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

除了别人已经提到的问题之外,我还看到另一个问题(缺点)-它不能用于C和C ++以外的任何语言。我知道这个问题是关于C ++的,但是如果您可以编写将是不错的C ++和Java代码的代码,那不是问题,那为什么不呢?也许某天某人将不得不将其移植到其他语言,并搜索由“魔术” UB 引起的错误,像这样的UB 无疑将是一场噩梦(特别是对于没有经验的C / C ++开发人员)。

这里有一个关于另一个类似的UB的问题。想象一下自己在不了解此UB的情况下试图查找此类bug。如果您想了解有关C / C ++中此类奇怪事物的更多信息,请从链接中阅读问题的答案,并查看 GREAT幻灯片。它将帮助您了解引擎盖下的内容及其工作方式。这不只是另一张充满“魔术”的幻灯片。我非常确定,即使是大多数有经验的C / c ++程序员也可以从中学到很多东西。


3

将我们的任何逻辑依赖于语言未定义的行为不是一个好主意。除了本文中提到/讨论的内容外,我还要提及的是,使用现代C ++方法/样式,此类程序可能无法编译。

在我之前的文章中提到了这一点,其中包含自动功能的优点和有用的链接。

https://stackoverflow.com/a/26170069/2724703

因此,如果我们更改上述代码并将实际类型替换为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);
    }
}

3

我喜欢你的思维方式。真的在盒子外面。但是,权衡确实不值得。内存运行时权衡是一回事,包括未定义的运行时行为不是

知道您使用诸如业务逻辑之类的“随机性”一定会让您感到非常不安。我不会的


3

使用7757每一个使用未初始化的变量,你被诱惑的地方。我从素数列表中随机选择了它:

  1. 它是定义的行为

  2. 保证不总是为0

  3. 这是首要的

  4. 在统计上可能与未初始化变量一样随机

  5. 它可能比未初始化的变量快,因为它的值在编译时是已知的


为了进行比较,请参见以下答案中的结果:stackoverflow.com/a/31836461/2963099
Glenn Teitelbaum 2015年

1

还有另一种可能性可以考虑。

现代编译器(ahem g ++)非常聪明,以至于他们遍历您的代码以查看哪些指令影响状态,哪些不影响状态,并且如果保证某个指令不影响状态,则g ++会简单地删除该指令。

所以这就是将会发生的事情。g ++肯定会看到您正在阅读,执行算术,保存实际上是垃圾值的东西,这会产生更多垃圾。由于不能保证新垃圾比旧垃圾有用,因此它只会消除循环。滚!

这种方法很有用,但这是我会做的。将UB(不确定行为)与rand()速度结合在一起。

当然要减少 rand()的执行,但将它们混合在一起,这样编译器就不会做您不希望做的任何事情。

而且我不会解雇你。


我发现很难相信编译器可以确定您的代码在做些愚蠢的事情并将其删除。我希望它只会优化未使用的代码,而不是不明智的代码。您有可复制的测试用例吗?无论哪种方式,UB的建议都是危险的。另外,GCC并不是唯一能胜任的编译器,因此将其选为“现代”是不公平的。
underscore_d

-1

如果正确完成,将未初始化的数据用于随机性不一定是一件坏事。实际上,OpenSSL正是这样做以植入PRNG。

显然,这种用法没有得到很好的记录,因为有人注意到Valgrind抱怨使用未初始化的数据并将其“修复”,从而导致PRNG中错误

因此,您可以这样做,但是您需要知道自己在做什么,并确保阅读代码的任何人都可以理解。


1
这将取决于您的编译器是否具有未定义的行为,正如我们从我的答案中看到的那样今天的clang无法满足他们的要求。
沙菲克·雅格慕

6
OpenSSL使用此方法作为熵输入并不能说明它有什么用。毕竟,他们使用的唯一其他熵源是PID。随机值不是很好。从依赖这样一个糟糕的熵源的人那里,我不会期望对他们的其他熵源有很好的判断力。我只是希望,目前维护OpenSSL的人更加聪明。
cmaster-恢复莫妮卡2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.