【发布时间】:2019-12-11 14:15:26
【问题描述】:
有时在开发过程中,例如在 C 代码中,您可能会意外地索引超出其最后一个元素的数组,从而导致读取本质上“随机”的内存块。我经常使用doubles 的数组,并注意到当发生这种情况时,从“随机”内存产生的double 通常非常大,例如大于1e+300。我想知道这是为什么。
如果用于解释 double 的 64 位是真正随机的,我希望 double 的指数从 0 均匀分布到 308(忽略指数的符号),由于使用科学(指数)表示法在内存中排列浮点数的方式。当然,内存中随机选择的位的值本身并不是随机分布的,而是对应于任何进程设置这些值的一些有意义的状态。
为了研究这种影响,我编写了以下 Python 3 脚本,该脚本绘制了从“随机”但未使用的内存中提取的真正随机生成的 doubles 和 doubles 的分布:
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')
运行此脚本的典型结果如下所示:
真正随机生成的doubles 的指数确实是均匀分布的。
从内存内容解释的doubles 的指数要么非常小,要么非常大。事实上,大部分未使用的内存都被清零了,从而产生了很多 0 值,这是有道理的。然而,正如我经常从跳出内存访问中体验到的那样,1e+300 附近的许多值也会出现。
我想解释一下这么大数量的超大doubles。
脚本运行注意事项
如果您想亲自试用该脚本,请注意您可能需要多次运行它才能显示任何有趣的内容。从内存内容中读取的每个数字都可能是0,在这种情况下它会告诉你。如果这种情况反复发生,请尝试降低N(使用的doubles 的数量)。
【问题讨论】:
-
“随机”实际上是指统一。均匀(随机)分布是其中每个元素具有相等的发生概率的分布(或者,对于连续分布,每个区间的发生概率与区间的大小成正比)。不同元素具有不同概率的分布仍然是随机的,只是不均匀。
-
内存布局倾向于将地址空间中的东西聚集在 0、0x7fff…、0x8000…和 0xffff…附近。所以值在 0x7ff 左右的指针的存在可以解释大的
double值。 -
@EricPostpischil 当这是预期的含义时,该问题专门使用了术语均匀随机。为什么内存布局倾向于围绕这些值进行聚类?
-
“如果……是真正随机的”,应该是“真正随机且均匀分布的”。 “真正随机生成”应该是“均匀分布的真正随机生成”。
-
内存布局围绕着这些价值观聚集,因为很久以前人们会坐在那里思考“我应该把东西放在哪里?”他们中的一些人从 0 开始,然后将下一件事放在 1,然后是 2,依此类推,或者是 4、8、12,或者他们需要的任何倍数。然后有人说好吧,我们已经把我们的代码和我们的一些数据放在那里了。嘿,我有个主意,让我们做一个堆栈。我们应该把它放在哪里?由于“底部”(0)已经被使用过,也许它们从地址空间的顶部开始(可能是当时的 0xffff)并向下工作。
标签: memory floating-point double random-access