推荐的初始化沙子的方法?

我需要一个“好”的方法来初始化 C + + 中的伪随机数生成器:

为了生成类随机的 数字,srand 通常被初始化 一些独特的价值,像那些 与执行时间有关 属性返回的值 函数时间(在头部声明) Ctime)每秒都不同,这 对大多数人来说都是独一无二的 需要随机选择。

Unixtime 对于我的应用程序来说不够独特。有什么更好的初始化方法吗?如果它是可移植的,那么还有额外的好处,但是代码将主要运行在 Linux 主机上。

我正在考虑做一些 pid/unixtime 数学运算来得到一个 int,或者可能从 /dev/urandom读取数据。

谢谢!

剪辑

是的,我实际上每秒多次启动我的应用程序,并且我遇到了冲突。

111516 次浏览

如果你需要一个更好的随机数生成器,不要使用 libc rand。取而代之的是直接使用像 /dev/random或者 /dev/urandom这样的东西(直接从中读入 int或者类似的东西)。

Libc rand 的唯一真正好处是,给定一个种子,它是可预测的,这有助于调试。

最好的办法就是用另一种伪随机数生成器。 梅森旋转算法(以及威奇曼-希尔)是我的建议。

Http://en.wikipedia.org/wiki/mersenne_twister

最好的答案是使用 <random>。如果您使用的是前 C + + 11版本,您可以查看 Boost 随机数的内容。

但如果我们讨论的是 rand()srand()
最简单的方法就是使用 time():

int main()
{
srand(time(nullptr));


...
}

请务必在程序开始时执行此操作,并且不要每次调用 rand()时都这样做!


附注:

注意 : 在下面的评论中有一个关于这是不安全的讨论(这是真的,但最终是不相关的(继续阅读))。因此,另一种选择是从随机设备 /dev/random(或其他一些安全的实数(呃)随机数生成器)进行播种。不要让这种错误的安全感诱惑你。这是我们使用的 rand()。即使你用一个出色的种子播种它,它仍然是可预测的(如果你有任何值,你可以预测下一个值的完整序列)。这只对生成 "pseudo"随机值有用。

如果你想“安全”,你可能应该使用 <random>(虽然我会做一些更多的阅读在一个安全知情的网站)。看看下面的答案作为一个起点: https://stackoverflow.com/a/29190957/14065为一个更好的答案。

次要说明: 使用随机设备实际上解决了问题,启动多个副本每秒比我原来的建议下(只是不是安全问题)。


回到最初的故事:

每次启动时,time ()将返回一个唯一的值(除非每秒多次启动应用程序)。在32位系统中,它大约每60年才会重复一次。

我知道你觉得时间不够独特但我觉得这很难相信。但大家都知道我错了。

如果要同时启动应用程序的大量副本,可以使用具有更好分辨率的计时器。但是,在这个值重复出现之前,您可能会面临一个较短的时间段的风险。

好的,如果您真的认为每秒钟要启动多个应用程序。
然后在计时器上使用更细的颗粒。

 int main()
{
struct timeval time;
gettimeofday(&time,NULL);


// microsecond has 1 000 000
// Assuming you did not need quite that accuracy
// Also do not assume the system clock has that accuracy.
srand((time.tv_sec * 1000) + (time.tv_usec / 1000));


// The trouble here is that the seed will repeat every
// 24 days or so.


// If you use 100 (rather than 1000) the seed repeats every 248 days.


// Do not make the MISTAKE of using just the tv_usec
// This will mean your seed repeats every second.
}

我建议您在 mozilla 代码中查看 unix _ Random.c 文件。(猜它是 mozilla/security/freebl/...)它应该在 freebl 库中。

在那里,它使用系统调用信息(如 pwd、 netstat... .)为随机数产生噪音; 它被编写为支持大多数平台(这可以让我得到额外的分数: D)。

在窗户上:

srand(GetTickCount());

提供一个比 time()更好的种子,因为它的单位是毫秒。

#include <stdio.h>
#include <sys/time.h>
main()
{
struct timeval tv;
gettimeofday(&tv,NULL);
printf("%d\n",  tv.tv_usec);
return 0;
}

Tv _ usec 以微秒为单位,这应该是可以接受的种子。

下面是我用于可以频繁运行(每秒多次)的小型命令行程序的代码:

unsigned long seed = mix(clock(), time(NULL), getpid());

这里的组合是:

// Robert Jenkins' 96 bit Mix Function
unsigned long mix(unsigned long a, unsigned long b, unsigned long c)
{
a=a-b;  a=a-c;  a=a^(c >> 13);
b=b-c;  b=b-a;  b=b^(a << 8);
c=c-a;  c=c-b;  c=c^(b >> 13);
a=a-b;  a=a-c;  a=a^(c >> 12);
b=b-c;  b=b-a;  b=b^(a << 16);
c=c-a;  c=c-b;  c=c^(b >> 5);
a=a-b;  a=a-c;  a=a^(c >> 3);
b=b-c;  b=b-a;  b=b^(a << 10);
c=c-a;  c=c-b;  c=c^(b >> 15);
return c;
}

你必须问自己的真正问题是你需要什么样的随机性。

Libc 随机是 立法会

无论你提供什么样的输入,随机性的质量都会很低。

如果您只是需要确保不同的实例有不同的初始化,那么您可以混合使用进程 id (getpid)、线程 id 和计时器。将结果与 xor 混合。对于大多数应用程序来说,熵应该足够了。

例如:

struct timeb tp;
ftime(&tp);
srand(static_cast<unsigned int>(getpid()) ^
static_cast<unsigned int>(pthread_self()) ^
static_cast<unsigned int >(tp.millitm));

为了获得更好的随机质量,可以使用/dev/urandom。

假设您有一个具有如下签名的函数:

int foo(char *p);

对于一个随机种子来说,熵的一个很好的来源是下面的散列:

  • 完整的结果 clock_gettime(秒和纳秒)没有扔掉低位-他们是最有价值的。
  • p的值,强制转换为 uintptr_t
  • p的地址,转到 uintptr_t

如果可用的话,至少第三个(也可能是第二个)从系统的 ASLR 获得熵(初始堆栈地址和当前堆栈地址都是随机的)。

我也会完全避免使用 rand/srand,这都是为了不触及全局状态,所以您可以对所使用的 PRNG 有更多的控制。但是,不管您使用什么 PRNG,上面的过程都是一个不错的(并且相当可移植)方法,可以在不做大量工作的情况下获得一些像样的熵。

C + + 11 random_device

如果您需要合理的质量,那么您首先不应该使用 rand () ; 您应该使用 <random>库。它提供了很多很棒的功能,比如不同质量/大小/性能权衡的各种引擎、可重入性和预定义的发行版,这样您就不会出错。根据您的实现,它甚至可以提供对不确定性随机数据(例如/dev/Random)的轻松访问。

#include <random>
#include <iostream>


int main() {
std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 eng(seed);


std::uniform_int_distribution<> dist{1,100};


for (int i=0; i<50; ++i)
std::cout << dist(eng) << '\n';
}

eng是随机性的一个来源,这里是一个内置的梅森旋转算法实现。我们使用 Random _ device 对它进行种子处理,在任何正式的实现中,它都将是一个非确定的 RNG,使用 eed _ seq 将超过32位的随机数据组合在一起。例如,在 libc + + Random _ device 中,默认情况下访问/dev/urandom (尽管您可以给它另一个文件来访问)。

接下来,我们创建一个分布,这样,给定一个随机性来源,对分布的重复调用将产生一个整数从1到100的均匀分布。然后我们继续重复使用分布并打印结果。

对于那些使用 Visual Studio 的人来说,这里还有另一种方式:

#include "stdafx.h"
#include <time.h>
#include <windows.h>


const __int64 DELTA_EPOCH_IN_MICROSECS= 11644473600000000;


struct timezone2
{
__int32  tz_minuteswest; /* minutes W of Greenwich */
bool  tz_dsttime;     /* type of dst correction */
};


struct timeval2 {
__int32    tv_sec;         /* seconds */
__int32    tv_usec;        /* microseconds */
};


int gettimeofday(struct timeval2 *tv/*in*/, struct timezone2 *tz/*in*/)
{
FILETIME ft;
__int64 tmpres = 0;
TIME_ZONE_INFORMATION tz_winapi;
int rez = 0;


ZeroMemory(&ft, sizeof(ft));
ZeroMemory(&tz_winapi, sizeof(tz_winapi));


GetSystemTimeAsFileTime(&ft);


tmpres = ft.dwHighDateTime;
tmpres <<= 32;
tmpres |= ft.dwLowDateTime;


/*converting file time to unix epoch*/
tmpres /= 10;  /*convert into microseconds*/
tmpres -= DELTA_EPOCH_IN_MICROSECS;
tv->tv_sec = (__int32)(tmpres * 0.000001);
tv->tv_usec = (tmpres % 1000000);




//_tzset(),don't work properly, so we use GetTimeZoneInformation
rez = GetTimeZoneInformation(&tz_winapi);
tz->tz_dsttime = (rez == 2) ? true : false;
tz->tz_minuteswest = tz_winapi.Bias + ((rez == 2) ? tz_winapi.DaylightBias : 0);


return 0;
}




int main(int argc, char** argv) {


struct timeval2 tv;
struct timezone2 tz;


ZeroMemory(&tv, sizeof(tv));
ZeroMemory(&tz, sizeof(tz));


gettimeofday(&tv, &tz);


unsigned long seed = tv.tv_sec ^ (tv.tv_usec << 12);


srand(seed);


}

也许有点夸张,但是对于快速间隔来说效果很好。

编辑: 经过进一步调查,rand _ s 可能是 Visual Studio 的一个很好的替代品,它不仅仅是一个安全的 rand () ,它完全不同,并且不使用来自 srand 的种子。我以为它和 Rand 几乎一模一样,只是“更安全”而已。

要使用 rand _ s,不要忘记在包含 stdlib.h 之前先定义 # Definition _ CRT _ RAND _ S。

在程序的顶部包含标题,然后写下:

srand(time(NULL));

在程序中声明随机数之前。下面是一个程序打印1到10之间的随机数的例子:

#include <iostream>
#include <iomanip>


using namespace std;


int main()
{
//Initialize srand
srand(time(NULL));


//Create random number
int n = rand() % 10 + 1;


//Print the number
cout << n << endl; //End the line


//The main function is an int, so it must return a value
return 0;
}

返回文章页面 c++11版投票最多的职位译者:

#include <ctime>
#include <random>
#include <thread>


...


const auto time_seed = static_cast<size_t>(std::time(0));
const auto clock_seed = static_cast<size_t>(std::clock());
const size_t pid_seed =
std::hash<std::thread::id>()(std::this_thread::get_id());


std::seed_seq seed_value { time_seed, clock_seed, pid_seed };


...
// E.g seeding an engine with the above seed.
std::mt19937 gen;
gen.seed(seed_value);

只要您的程序只在 Linux 上运行(并且您的程序是一个 ELF 可执行文件) ,就可以保证内核在 ELF aux 向量中为您的进程提供一个唯一的随机种子。内核为您提供16个随机字节,每个进程不同,您可以使用 getauxval(AT_RANDOM)获得这些字节。对于 srand,只需要使用它们中的一个 int,如下所示:

#include <sys/auxv.h>


void initrand(void)
{
unsigned int *seed;


seed = (unsigned int *)getauxval(AT_RANDOM);
srand(*seed);
}

这可能也适用于其他基于 ELF 的系统。我不确定在 Linux 之外的系统上实现了哪些 aux 值。

假设 srand () + rand ()的随机性对于您的目的已经足够了,诀窍在于选择 srand 的最佳种子。Time (NULL)是一个很好的起点,但是如果在同一秒内启动多个程序实例,就会遇到问题。添加 pid (进程 id)是一种改进,因为不同的实例将获得不同的 pid。我会把 pid 乘以一个因子,使它们分布得更广。

但是,让我们假设您正在使用它来处理一些嵌入式设备,并且在同一个网络中有几个嵌入式设备。如果它们都同时供电,并且在启动时自动启动程序的几个实例,它们仍然可能获得相同的时间和 pid,并且所有设备将生成相同的“随机”数序列。在这种情况下,您可能需要为每个设备添加一些唯一标识符(比如 CPU 序列号)。

然后,拟议的启动将是:

srand(time(NULL) + 1000 * getpid() + (uint) getCpuSerialNumber());

在 Linux 机器上(至少在我测试过的 Raspberry Pi 中) ,你可以实现以下函数来获得 CPU 序列号:

// Gets the CPU Serial Number as a 64 bit unsigned int. Returns 0 if not found.
uint64_t getCpuSerialNumber() {


FILE *f = fopen("/proc/cpuinfo", "r");
if (!f) {
return 0;
}


char line[256];
uint64_t serial = 0;
while (fgets(line, 256, f)) {
if (strncmp(line, "Serial", 6) == 0) {
serial = strtoull(strchr(line, ':') + 2, NULL, 16);
}
}
fclose(f);


return serial;
}