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

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

在实践中,它可能(可能)实际上(kindof)工作。 从x86 [-64]体系结构上未初始化的寄存器中读取,确实会产生垃圾结果,并且可能不会做任何坏事(例如Itanium,在这种情况下, 寄存器可以被标记为无效 ,以便读取像NaN一样传播错误)。

有两个主要问题:

  1. 它不会特别随机。 在这种情况下,你正在从堆栈中读取,所以你会得到以前的东西。 这可能是有效的随机的,完全结构化的,你十分钟前input的密码,或者你祖母的cookies配方。

  2. 这是坏的(大写'B')的做法,让这样的事情蔓延到你的代码。 从技术上讲,编译器可以插入reformat_hdd(); 每次你读一个未定义的variables。 它不会 ,但你不应该这样做。 不要做不安全的事情。 你做出的例外越less,你始终因意外错误而变得越安全。

    UB更紧迫的问题是它使你的整个程序的行为不确定。 现代编译器可以使用它来缩短代码的大部分时间,甚至可以及时回溯 。 和UB一起玩就像维修工程师拆解一个活的核反应堆。 有太多的事情会出错,你可能不会知道一半的基本原理或实施的技术。 这可能是好的,但你仍然不应该让它发生。 看看其他好的答案的细节。

另外,我会解雇你。

让我清楚地说: 我们不在程序中调用未定义的行为 。 这段时间永远不是一个好主意。 这个规则很less例外。 例如,如果你是一个实现offsetof的库实现者 。 如果你的情况属于这种例外,你可能已经知道这一点。 在这种情况下,我们知道使用未初始化的自动variables是未定义的行为 。

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

我们可以阅读CERT的“ 危险优化”和“因果关系损失 ”,其中包括:

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

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

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

特别是对于不确定的值,C标准缺陷报告451:未初始化的自动variables的不稳定性使得一些有趣的阅读成为可能。 它还没有解决,但引入了价值观摇摆不定的概念,这意味着价值的不确定性可以通过程序传播,并可以在程序的不同点有不同的不确定的价值。

我不知道发生这种情况的任何例子,但在这一点上,我们不能排除。

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

你不可能得到随机值。 一个编译器可以优化掉这个循环。 例如,在这个简化的情况下:

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

铿锵优化它( 看到它住 ):

 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 

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

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

如果寄存器碰巧保存了一个特殊的非物件值,读取寄存器陷阱,除了几个指令[…]

其他重要的笔记

有趣的是,在UB Canaries项目中注意到gcc和clang之间的差异,他们是否愿意利用关于未初始化内存的未定义行为。 文章笔记( 重点是我的 ):

当然,我们需要完全明白,任何这样的期望都与语言标准无关,而与编译器碰巧做的事情无关,要么是因为编译器的提供者不愿意利用这个UB ,因为他们还没有到处利用它呢 。 当编译器提供者没有真正的保证时, 我们想说尚未开发的UB是时间炸弹 :他们正在等待下个月或者明年编译器变得更加激进。

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

可怕的是, 几乎所有基于未定义行为的优化都可能在未来的任何时候开始被错误的代码触发 。 内联,循环展开,内存升级和其他优化将继续变得更好,其存在的重要原因是揭示次优化。

对我来说,这是深深的不满,部分原因是编译器不可避免地被指责,也因为这意味着庞大的C代码体系正在等待爆炸的地雷。

为了完整起见,我应该提到,实现可以select使未定义的行为明确定义,例如gcc允许 在C ++中 通过联合进行types窜改 ,这似乎是未定义的行为 。 如果是这种情况,实施应该logging下来,这通常是不可移植的。

不,这太可怕了。

使用未初始化variables的行为在C和C ++中都是未定义的,并且这种scheme不太可能具有理想的统计特性。

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

我所知道的最快的发生器要求你使用一个uint32_t作为伪随机variablesI的types,并使用它

I = 1664525 * I + 1013904223

生成连续的值。 你可以select任何初始值( I称之为种子 )。 显然,你可以用内联代码。 无符号types的标准保证环绕作为模数。 (这个数字常量是由那个杰出的科学程序员Donald Knuth精心挑选的。)

好问题!

未定义并不意味着它是随机的。 想想看,你在全局未初始化variables中得到的值是由系统或你的/其他应用程序运行的。 根据您的系统不再使用内存和/或系统和应用程序生成什么样的值,您可能会得到:

  1. 总是一样。
  2. 成为一小组价值观之一。
  3. 获取一个或多个小范围的值。
  4. 在16/32/64位系统上查看指针可以从2/4/8中分割出许多值

您将得到的值完全取决于系统和/或应用程序留下的非随机值。 所以,确实会有一些噪音(除非你的系统不再使用内存),但是你所画的值池决不是随机的。

事情变得更糟糕的局部variables,因为这些直接来自你自己的程序的堆栈。 在执行其他代码的过程中,程序实际上会写入这些堆栈位置的机会很大。 我估计在这种情况下碰运气的可能性非常低,而你所做的一个“随机”的代码改变试试这个运气。

阅读关于随机性 。 你会看到随机性是一个非常具体的,很难获得的财产。 这是一个常见的错误,认为如果你只是采取一些难以追踪的东西(如你的build议),你会得到一个随机的价值​​。

许多好的答案,但是让我补充一点,强调在确定性的计算机中,没有什么是随机的。 这对于伪随机数发生器产生的数字和堆栈上为C / C ++局部variables保留的内存区域中看似“随机”的数字都是正确的。

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

由一个好的伪随机生成器生成的数字具有使它们在统计上类似于真正的随机抽取的特性。 例如,分配是统一的。 循环长度很长:循环重复之前可以获得数百万个随机数。 该序列不是自相关的:例如,如果您每拍2,3或27号码,或者查看生成的数字中的特定数字,您都不会看到奇怪的图案出现。

相反,堆栈上留下的“随机”数字没有这些属性。 它们的值和performance的随机性完全取决于程序的构build方式,编译方式以及编译器如何优化它们。 举例来说,这是一个独立程序的变化:

 #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 

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

所以这种行为绝对不是随机的,这样得到的数字也没有任何一个写得很好的伪随机数发生器的理想特性。 事实上,在大多数现实生活中,他们的价值将是重复的,高度相关的。

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

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

虽然在理论上当进入UB的时候, 任何事情都可能发生 (包括一个守护进程从你的鼻子飞出 )通常意味着编译器作者根本不会在意,对于局部variables来说,值是堆栈内存中的任何值。

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

当然,你不能指望它是一个体面的随机发生器。

未定义的行为是未定义的。 这并不意味着你得到一个未定义的值,这意味着该程序可以做任何事情 ,仍然符合语言规范。

一个好的优化编译器应该采取

 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。 这当然比任何替代方法都快。 它有缺点,它不会做任何事情,但这是未定义的行为的缺点。

尚未提到,但是调用未定义行为的代码path被允许做任何编译器想要的,例如

 void updateEffect(){} 

这肯定比你正确的循环更快,而且由于UB,它是完全一致的。

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

你特定的代码示例可能不会做你所期望的。 在技​​术上,循环的每次迭代都会为r,g和b值重新创build局部variables,但在实践中,它是完全相同的栈内存空间。 因此,每次迭代都不会得到重新随机化,无论r,g和b是如何随机分配和初始化,最终都会为1000种颜色中的每一种指定相同的3个值。

事实上,如果确实有效的话,我会很好奇它是什么样的重新随机化。 我能想到的唯一的事情就是将交叉存取的中断封装在堆栈上,这是不太可能的。 也许内部优化把这些寄存器variables而不是真正的内存位置(寄存器在循环中进一步被重新使用的地方)作为寄存器variables,也可以做到这一点,特别是如果设置的可见性函数特别是寄存器饥饿的话。 尽pipe如此,还是远离随机。

这里的大多数人提到了不确定的行为。 未定义也意味着你可能会得到一些有效的整数值(幸运的是),在这种情况下,这会更快(因为没有兰德函数调用)。 但是不要实际使用它。 我相信这将是可怕的结果,因为运气不是一直在你身边。

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

 A_Function_that_use_a_lot_the_Stack(); updateEffect(); 

如果函数A_Function_that_use_a_lot_the_Stack()始终保持相同的初始化状态,则会使堆栈上的数据相同。 这些数据就是我们所说的updateEffect()始终是相同的值!

我进行了一个非常简单的testing,而且不是随机的。

 #include <stdio.h> int main() { int a; printf("%d\n", a); return 0; } 

每次我运行这个程序,它都会打印相同的数字(在我的例子中是32767 ) – 不能比这个数字less得多。 这大概是堆栈中运行时库中的启动代码。 由于每次程序运行时都使用相同的启动代码,运行之间程序中没有其他变化,结果是完全一致的。

你需要有一个“随机”的定义。 一个明智的定义涉及到你所得到的价值应该没有什么关系。 这是你可以衡量的东西。 以可控,可重复的方式实现也不是微不足道的。 所以,未定义的行为当然不是你正在寻找的。

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

然而,即使在这种情况下,人们总是应该假定,如果任何字节的组合都是特别棘手的,那么读取它总是会产生这种字节模式(如果某种模式在生产中会是无理取闹的,而不是在开发中,模式在代码生产之前不会出现)。

读取未初始化的内存可能是有用的,作为embedded式系统中的随机生成策略的一部分,其中可以确信从上次系统通电开始,内存从未被写入实质上非随机的内容,并且如果制造用于存储器的过程导致其开机状态以半随机方式变化。 即使所有设备总是产生相同的数据,代码也应该工作,但是在例如一组节点中的每一个都需要尽可能快地select任意唯一ID的情况下,具有“非常随机的”发生器,其给半个节点提供相同的初始ID可能比没有任何初始的随机性来得好。

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

What most compilers will do for local variables is to grab some space for them on the stack, but not bother setting it to anything (the standard says they don't need to, so why slow down the code you're generating?).

In this case, the value you'll get will depend on what was on previously on the stack – if you call a function before this one that has a hundred local char variables all set to 'Q' and then call you're function after that returns, then you'll probably find your "random" values behave as if you've memset() them all to 'Q's.

Importantly for your example function trying to use this, these values wont change each time you read them, they'll be the same every time. So you'll get a 100 stars all set to the same colour and visibility.

Also, nothing says that the compiler shouldn't initialize these value – so a future compiler might do so.

In general: bad idea, don't do it. (like a lot of "clever" code level optimizations really…)

As others have already mentioned, this is undefined behavior ( UB ), but it may "work".

Except from problems already mentioned by others, I see one other problem (disadvantage) – it will not work in any language other than C and C++. I know that this question is about C++, but if you can write code which will be good C++ and Java code and it's not a problem then why not? Maybe some day someone will have to port it to other language and searching for bugs caused by "magic tricks" UB like this definitely will be a nightmare (especially for an inexperienced C/C++ developer).

Here there is question about another similar UB. Just imagine yourself trying to find bug like this without knowing about this UB. If you want to read more about such strange things in C/C++, read answers for question from link and see this GREAT slideshow. It will help you understand what's under the hood and how it's working; it's not not just another slideshow full of "magic". I'm quite sure that even most of experienced C/c++ programmers can learn a lot from this.

Not a good idea to rely our any logic on language undefined behaviour. In addition to whatever mentioned/discussed in this post, I would like to mention that with modern C++ approach/style such program may not be compile.

This was mentioned in my previous post which contains the advantage of auto feature and useful link for the same.

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

So, if we change the above code and replace the actual types with auto , the program would not even compile.

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

I like your way of thinking. Really outside the box. However the tradeoff is really not worth it. Memory-runtime tradeoff is a thing, including undefined behavior for runtime is not .

It must give you a very unsettling feeling to know you are using such "random" as your business logic. I woudn't do it.

Use 7757 every place you are tempted to use uninitialized variables. I picked it randomly from a list of prime numbers:

  1. it is defined behavior

  2. it is guaranteed to not always be 0

  3. it is prime

  4. it is likely to be as statistically random as uninitualized variables

  5. it is likely to be faster than uninitialized variables since its value is known at compile time

There is one more possibility to consider.

Modern compilers (ahem g++) are so intelligent that they go through your code to see what instructions affect state, and what don't, and if an instruction is guaranteed to NOT affect the state, g++ will simply remove that instruction.

So here's what will happen. g++ will definitely see that you are reading, performing arithmetic on, saving, what is essentially a garbage value, which produces more garbage. Since there is no guarantee that the new garbage is any more useful than the old one, it will simply do away with your loop. BLOOP!

This method is useful, but here's what I would do. Combine UB (Undefined Behaviour) with rand() speed.

Of course, reduce rand() s executed, but mix them in so compiler doesn't do anything you don't want it to.

And I won't fire you.

Using uninitialized data for randomness is not necessarily a bad thing if done properly. In fact, OpenSSL does exactly this to seed its PRNG.

Apparently this usage wasn't well documented however, because someone noticed Valgrind complaining about using uninitialized data and "fixed" it, causing a bug in the PRNG .

So you can do it, but you need to know what you're doing and make sure that anyone reading your code understands this.