为啥从内存中插入的“随机”数字通常非常大?
Posted
技术标签:
【中文标题】为啥从内存中插入的“随机”数字通常非常大?【英文标题】:Why are "random" numbers plugged from memory often extremely large?为什么从内存中插入的“随机”数字通常非常大? 【发布时间】:2019-12-11 14:15:26 【问题描述】:有时在开发过程中,例如在 C 代码中,您可能会意外地索引超出其最后一个元素的数组,从而导致读取本质上“随机”的内存块。我经常使用double
s 的数组,并注意到当发生这种情况时,从“随机”内存产生的double
通常非常大,例如大于1e+300
。我想知道这是为什么。
如果用于解释 double
的 64 位是真正随机的,我希望 double
的指数从 0
均匀分布到 308
(忽略指数的符号),由于使用科学(指数)表示法在内存中排列浮点数的方式。当然,内存中随机选择的位的值本身并不是随机分布的,而是对应于任何进程设置这些值的一些有意义的状态。
为了研究这种影响,我编写了以下 Python 3 脚本,该脚本绘制了从“随机”但未使用的内存中提取的真正随机生成的 double
s 和 double
s 的分布:
import random, struct
import numpy as np
import matplotlib.pyplot as plt
N = 10000
def random_floats(N=1):
return np.array(struct.unpack('d'*N, bytes(random.randrange(256) for _ in range(8*N))))
def exp_hist(a, label=None):
a = a[~np.isnan(a)]
a = a[~np.isinf(a)]
a = a[a != 0]
if len(a) == 0:
print('Zeros only!')
return
a = np.abs(np.log10(np.abs(a)))
plt.hist(a, range=(0, 350), density=True, alpha=0.8, label=label)
# Floats generated from uniformly random bits
a = random_floats(N)
exp_hist(a, 'random')
# Floats generated from memory content
a = np.empty(N)
exp_hist(a, 'memory')
plt.xlabel('exponent')
plt.legend()
plt.savefig('plot.png')
运行此脚本的典型结果如下所示:
真正随机生成的double
s 的指数确实是均匀分布的。
从内存内容解释的double
s 的指数要么非常小,要么非常大。事实上,大部分未使用的内存都被清零了,从而产生了很多 0
值,这是有道理的。然而,正如我经常从跳出内存访问中体验到的那样,1e+300
附近的许多值也会出现。
我想解释一下这么大数量的超大double
s。
脚本运行注意事项
如果您想亲自试用该脚本,请注意您可能需要多次运行它才能显示任何有趣的内容。从内存内容中读取的每个数字都可能是0
,在这种情况下它会告诉你。如果这种情况反复发生,请尝试降低N
(使用的double
s 的数量)。
【问题讨论】:
“随机”实际上是指统一。均匀(随机)分布是其中每个元素具有相等的发生概率的分布(或者,对于连续分布,每个区间的发生概率与区间的大小成正比)。不同元素具有不同概率的分布仍然是随机的,只是不均匀。 内存布局倾向于将地址空间中的东西聚集在 0、0x7fff…、0x8000…和 0xffff…附近。所以值在 0x7ff 左右的指针的存在可以解释大的double
值。
@EricPostpischil 当这是预期的含义时,该问题专门使用了术语均匀随机。为什么内存布局倾向于围绕这些值进行聚类?
“如果……是真正随机的”,应该是“真正随机且均匀分布的”。 “真正随机生成”应该是“均匀分布的真正随机生成”。
内存布局围绕着这些价值观聚集,因为很久以前人们会坐在那里思考“我应该把东西放在哪里?”他们中的一些人从 0 开始,然后将下一件事放在 1,然后是 2,依此类推,或者是 4、8、12,或者他们需要的任何倍数。然后有人说好吧,我们已经把我们的代码和我们的一些数据放在那里了。嘿,我有个主意,让我们做一个堆栈。我们应该把它放在哪里?由于“底部”(0)已经被使用过,也许它们从地址空间的顶部开始(可能是当时的 0xffff)并向下工作。
【参考方案1】:
您可能会在内存中找到许多不同的东西,但其中有很多会映射到非常大或非常小的浮点数、无穷大或 NaN。在下文中,“FP”表示 IEEE 754 64 位二进制浮点数。
首先,因为他们已经在 cmets 中讨论过这个问题,所以请考虑地址。 64 位地址的所有指数位通常为零(内存的低端),或所有指数位的高位(内存的高端,通常是堆栈地址)。如果所有指数位都为高,则它是无穷大或 NaN,程序似乎忽略了它。如果所有指数位都为零,则它是次正规数或零。次正规数均小于2.3E-308,计为指数308。
现在考虑 32 位整数,这是另一种非常常见的数据形式。映射到有限 FP 的负 32 位二进制补码整数为 -1048577 或更小。像 -42 或 -1 这样的数字映射到 NaN,被程序忽略。类似地,中等值正整数的所有指数位为零,因此映射到次正规数,映射到直方图的大指数端。即使是小的正态数也对应于惊人的大整数。例如,1e-300 的前 32 位具有整数值 27,618,847。
对于指针和整数,对所有具有相同值的指数位都有强烈的偏见,要么全为零,要么全为一。所有一个都是 NaN 或无穷大,程序不计算在内。所有的零都是次正规的,算作非常大的指数。
【讨论】:
以上是关于为啥从内存中插入的“随机”数字通常非常大?的主要内容,如果未能解决你的问题,请参考以下文章