当前位置: 首页 > 知识库问答 >
问题:

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

云卓
2023-03-14

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

也比其他随机数生成器快?

共有3个答案

裴令秋
2023-03-14

不,太可怕了。

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

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

据我所知,最快的生成器要求使用uint32\t作为伪随机变量I的类型,并使用

I=1664525*I 1013904223

生成连续的值。您可以选择任何初始值的I(称为种子),你喜欢。显然你可以内联编码。无符号类型的标准保证环绕充当模数。数字常数是由杰出的科学程序员唐纳德·克努特手工挑选的。)

胡越
2023-03-14

让我明确地说:我们不会在程序中调用未定义的行为。这从来都不是一个好主意,句号。此规则很少有例外;例如,如果您是实现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 ;
    }
}

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

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

注意,如果我们在安腾上,可能会得到一个陷阱值:

[...]如果寄存器碰巧持有一个特殊的非物质值,则读取寄存器陷阱,除了少数指令[…]

其他重要注意事项

有趣的是注意到UB Canaris项目中提到的gcc和clang之间的差异,即它们对于未初始化内存的未定义行为的利用程度。文章指出(我的重点):

当然,我们需要完全清楚地认识到,任何这样的期望都与语言标准无关,都与某个特定的编译器所做的事情有关,要么是因为该编译器的提供者不愿意利用该UB,要么仅仅是因为他们还没有来得及利用它。当编译器提供商没有提供真正的保证时,我们喜欢说尚未开发的UBs是定时炸弹:他们正等待着下个月或明年编译器变得更具攻击性时爆炸。

正如Matthieu M.指出的,每个C程序员应该知道的关于未定义行为的知识#2/3也与这个问题相关。除其他外,它说(强调我的):

需要意识到的重要和可怕的事情是,任何基于未定义行为的优化都可能在将来的任何时候在错误代码上触发。内联、循环展开、内存提升和其他优化将不断改进,它们存在的一个重要原因是公开类似于上述的二次优化。

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

为了完整性起见,我可能应该提到,实现可以选择定义好未定义的行为,例如,gcc允许通过联合进行类型双关,而在C中,这看起来像是未定义的行为。如果是这种情况,实现应该记录它,这通常是不可移植的。

桂宏旷
2023-03-14

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

实际上,它(可能)会(某种程度上)起作用。在x86[-64]体系结构上读取未初始化的寄存器确实会产生垃圾结果,并且可能不会产生任何不良影响(与安腾相反,安腾可以将寄存器标记为无效,以便读取会传播错误,如NaN)。

不过,有两个主要问题:

>

  • 它不会特别随机。在本例中,您正在从堆栈中读取数据,因此您将获得以前存在的内容。这可能是随机的,完全结构化的,十分钟前输入的密码,或者你祖母的饼干配方。

    让类似这样的东西潜入代码是不好的(大写“B”)做法。从技术上讲,编译器可以插入reformat_hdd() 每次读取未定义的变量时。不会的,但无论如何你不应该这么做。不要做不安全的事情。你犯的例外越少,你就越安全,永远不会犯意外错误。

    UB更紧迫的问题是,它使整个程序的行为无法定义。现代编译器可以使用它来删除大量代码,甚至可以追溯到时间。玩UB就像维多利亚时代的工程师拆除一个活的核Reactor。有无数的事情会出错,你可能不知道一半的基本原理或实现的技术。也许没关系,但你还是不应该让它发生。请看其他漂亮的答案以了解详细信息。

    还有,我会解雇你。

  •  类似资料:
    • 我对python相当陌生,我想知道局部变量是如何工作的。让我们从一个简单方法的示例开始: 让我们假设local_dict像一种常量变量一样使用。这里有一个问题:它是在每次调用do_sth()时创建的,还是创建一次并保存在do_sth()内部的某个地方?

    • 问题内容: UI类在View中,导入已完成,但是在最后一个表达式中我得到了错误。 我是Java的入门者,但我不明白为什么我不允许这样使用它。 问题答案: 如果要在Java方法中声明变量/对象,则需要对其进行初始化。 简单来说 在您的情况下,它是一个正在访问方法的对象,因此,如果不初始化它,就像 它会给你一个NULL指针异常。 希望能帮助到你。

    • 本文向大家介绍tensorflow 初始化未初始化的变量实例,包括了tensorflow 初始化未初始化的变量实例的使用技巧和注意事项,需要的朋友参考一下 今日在Stack Overflow上看到一个问如何只初始化未初始化的变量,有人提供了一个函数,特地粘贴过来共大家品鉴: 通过tf.global_variables()返回一个全局变量的列表global_vars, 然后以python列表解析式的

    • 考虑到下面的代码,为什么即使优惠券、偏移量和基已在if语句中初始化,编译器仍会在第5行抱怨?由于“percent”变量的默认值为0.0,因此if语句将运行并设置这些值。

    • 我在后续代码中遇到了一个我之前不知道的行为。 考虑1st情况: 正如预期的那样,编译器在str为null-null指针访问时向我显示以下警告:变量str只能在此位置为null。 现在,当我移动该变量时,静态final字段初始化为null: 现在,编译器没有显示任何警告。好了,编译器应该知道,str是最终的,在代码的任何一点上都不会改变它的值。假设它是空的,那么以后肯定会导致空点异常。 尽管编译器在

    • 下面的示例类无法编译: 此代码的编译错误消息是: 但是,对于包含以下方法的类,Java不会生成任何错误消息: 关于初始化及其要求,为什么Java对最终实例变量和最终局部变量的处理不同?谢谢