【问题标题】:Recommended way to initialize srand?初始化 srand 的推荐方法?
【发布时间】:2010-09-24 06:56:32
【问题描述】:

我需要一种“好的”方法来初始化 C++ 中的伪随机数生成器。我发现an article 指出:

为了生成随机类 数字,srand 通常被初始化 到一些独特的价值,比如那些 与执行时间有关。为了 例如,返回的值 功能时间(在标题中声明 ctime) 每秒都不同,这 对大多数人来说足够独特 随机需求。

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

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

谢谢!

编辑

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

【问题讨论】:

  • 为什么 time() 不够?您是否在一秒钟内多次启动应用程序?请注意,您应该只在应用程序中调用一次 srand()。
  • 如果 time() - 或 gettimeofday() - 还不够,那么 rand() 可能对您来说还不够好。 PRNG 根本不需要非常好。密码随机性很难 - 使用密码库。
  • 实际上至少 MS CRT 将种子保存在 TLS 中,因此在这种情况下,您实际上需要为每个线程初始化一次 srand() 。但不确定 GCC 是如何存储种子的。
  • 更多关于用微秒初始化 srand() 的信息:guyrutenberg.com/2007/09/03/seeding-srand
  • 相关:How I hacked Hacker News。 HN 以毫秒为单位使用时间作为种子,然后使用 RNG 生成登录 cookie。攻击者可以通过登录他们的帐户,接收 cookie 来恢复种子,然后枚举可能的种子并根据 cookie 检查每个种子,直到找到匹配项。有了正确的种子,他们就可以预测其他用户的 cookie,并以这些用户的身份在网站上执行操作。

标签: c++ random srand


【解决方案1】:

如果您需要更好的随机数生成器,请不要使用 libc rand。而是直接使用/dev/random/dev/urandom 之类的东西(直接从中读取int 或类似的东西)。

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

【讨论】:

  • 在 Windows 上可以使用 rand_s。
  • 你不应该从/dev/urandom 重复阅读随机数,而只是为了种子。它要慢得多,并且会耗尽系统熵池。
  • 这里只是吹毛求疵:每个 PRNG 都是可预测的,给定相同的种子。这就是 PRNG 中 Pseudo 的含义。
  • @vanneto,当您弄清楚如何播种 /dev/random 以使其完全可预测时,请告诉我:-P。它也是一个 PRNG。
  • std::random_device 是 glibc 中 /dev/urandom 的可移植包装器:stackoverflow.com/a/13004555/895245
【解决方案2】:

最好的方法是使用另一个伪随机数生成器。 Mersenne twister(和 Wichmann-Hill)是我的推荐。

http://en.wikipedia.org/wiki/Mersenne_twister

【讨论】:

  • 我正要推荐这个生成器。我已经将它用于许多科学建模问题,它产生的结果比我见过的替代品要好得多。 math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
  • 如果你有更好的RNG,为什么要用它来播种标准的RNG?只用更好的!
  • 这基本上是我的建议:-P
  • @Pax:使用另一个 PRNG,不要使用 PRNG 作为种子!用更好的 PRNG 播种 PRNG,谁会做这样的事情? :D
  • 这根本解决不了问题,如何播种 MT PRNG?
【解决方案3】:

最好的答案是<random>。如果您使用的是 C++11 之前的版本,您也可以查看 Boost random number stuff。

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

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

    ...
}

请务必在程序开始时执行此操作,而不是每次调用 rand()


旁注:

注意:下面的 cmets 讨论了这是不安全的(这是真的,但最终不相关(继续阅读))。因此,另一种选择是从随机设备/dev/random(或其他一些安全的实数(er)随机数生成器)播种。 但是:不要让这让你陷入虚假的安全感。这是我们正在使用的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.
 }

【讨论】:

  • 实际上,我正在启动我的应用程序的多个实例 :)
  • 我在某个地方看到了适用于 timeval/gettimeofday 的解决方案......啊,是的,我自己的!您批评它非常糟糕,尽管它会重复的可能性是 1/1000000(与您所做的假设相同)。而你的是 1/60000。所以,这是我的解决方案,更糟糕的是,它被呈现为你的。
  • 1) 不要假设时钟分辨率是 1/1000000。 2)我的概率是 1/2147483648(注意 tv_sec)。 3) 种子重复间隔 24 天(应用之间的交互也很重要)。
  • "种子重复间隔 24 天" 你在一秒钟内有 1000 次碰撞。
  • tv_usec 除以 1000 会丢弃所有熵。毫秒很容易预测,实际上在不到一毫秒的时间内运行两次程序很容易......
【解决方案4】:

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

它使用系统调用信息(如 pwd、netstat ....)为随机数生成噪声;它是为支持大多数平台而编写的(这可以给我加分:D)。

【讨论】:

    【解决方案5】:

    在窗户上:

    srand(GetTickCount());
    

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

    【讨论】:

    • 您需要小心:“如果系统连续运行 49.7 天,时间将回绕为零。为避免此问题,请使用 GetTickCount64 函数”。如果它达到始终为零的程度,这将产生不好的结果。
    • @floele 环绕零意味着它每 49.7 天再次从零开始计数,而不是在 49.7 天的正常运行时间后无法使用。
    【解决方案6】:
    #include <stdio.h>
    #include <sys/time.h>
    main()
    {
         struct timeval tv;
         gettimeofday(&tv,NULL);
         printf("%d\n",  tv.tv_usec);
         return 0;
    }
    

    tv.tv_usec 以微秒为单位。这应该是可以接受的种子。

    【讨论】:

    • 这是一个非常糟糕的主意。种子每秒重复一次。如果您多次启动该应用程序,您很有可能会获得相同的种子。
    • 非常糟糕? 1/1000000(是的,这是百万分之一)重复的机会?如果你觉得幸运,就去买彩票吧! 9.5367431640625e-07(小于一微秒)用python测量:from time import time abs(time()-time()) #on windows use clock()-clock()
    • 如果您只买一张彩票,那就太好了。但是,如果您每秒购买 1 张彩票,您中奖的频率是多少。概率说一年有 36 次点击。我的方法 1/2147483648 机会每 24 天重复一次,在 1 年内,每年有 1/143165576 次点击!
    • 我使用 tv_sec: 基于时代。它每 60 年重复一次。将此模块乘以 1000(我通过乘以 1000 来敲除最高位)。这为您提供了一个每 24 天重复一次的值。然后我将 uSec 插入底部位。因此,我有一个每 24 天重复一次的毫秒精度时间。
    • 这个讨论就是一个例子,说明为什么 rand(如密码学)难以正确,以及为什么人们应该使用 boost::rand 而不是自己尝试。除非您是该主题的专家,否则伪随机数会变得不随机。
    【解决方案7】:

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

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

    【讨论】:

    【解决方案8】:

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

    libc random 是一个LCG

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

    如果您只是需要确保不同的实例具有不同的初始化,您可以混合使用进程 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。您可以使用 boost::thread 和 boost::date_time 使上述代码可移植。

    【讨论】:

      【解决方案9】:

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

      int foo(char *p);
      

      随机种子的一个很好的熵来源是以下内容的哈希:

      • clock_gettime 的完整结果(秒和纳秒)而不丢弃低位 - 它们是最有价值的。
      • p 的值,转换为uintptr_t
      • p的地址,转换为uintptr_t

      如果可用的话,至少第三个,也可能是第二个,从系统的 ASLR 中获取熵(初始堆栈地址,因此当前堆栈地址有些随机)。

      我也会完全避免使用rand/srand,这既是为了不触及全局状态,也是为了更好地控制所使用的 PRNG。但是,无论您使用什么 PRNG,上述过程都是一种很好(并且相当便携)的方法,无需大量工作即可获得一些体面的熵。

      【讨论】:

        【解决方案10】:

        C++11 random_device

        如果您需要合理的质量,那么您首先不应该使用 rand();你应该使用&lt;random&gt; 库。它提供了许多很棒的功能,例如用于不同质量/大小/性能权衡的各种引擎、重入和预定义分布,因此您最终不会弄错它们。它甚至可以提供对非确定性随机数据(例如 /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 是随机性的来源,这里是 mersenne twister 的内置实现。我们使用 random_device 播种它,在任何体面的实现中都将是一个非确定性的 RNG,并使用 seed_seq 组合超过 32 位的随机数据。例如在 libc++ random_device 默认访问 /dev/urandom (尽管你可以给它另一个文件来访问)。

        接下来,我们创建一个分布,在给定随机源的情况下,重复调用该分布将产生从 1 到 100 的统一整数分布。然后我们继续重复使用该分布并打印结果。

        【讨论】:

          【解决方案11】:

          对于那些使用 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);
          
          }
          

          可能有点矫枉过正,但适用于快速间隔。找到了 gettimeofday 函数 here

          编辑:经过进一步调查,rand_s 可能是 Visual Studio 的一个不错的选择,它不仅仅是一个安全的 rand(),它完全不同,并且不使用 srand 的种子。我以为它与 rand 几乎相同,只是“更安全”。

          要使用 rand_s,请不要忘记在包含 stdlib.h 之前#define _CRT_RAND_S。

          【讨论】:

          • 在您的链接帖子中,它使用/ 1000000UL 表示gettimeofday。据我所知,您的 * 0.000001 会产生奇怪的(负面)结果。这应该在您的代码中修复吗?
          • 它是从stackoverflow.com/a/5197874/990618 复制而来的,看不出你是如何得到底片、错字、漏行的?
          • 不知道(虽然我很确定这不是错字),我没有进一步调查,最终还是使用了rand_s。但是这个帖子social.msdn.microsoft.com/Forums/vstudio/en-US/… 也使用了/ 1000000UL 方法,对我来说似乎更安全。因此,如果有人正在使用此代码,则应在此行验证它是否正常工作。
          【解决方案12】:

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

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

          【讨论】:

          • 尽管 time(NULL) 在某些情况下是可以接受的,但您不应该在不暴露缺点的情况下推荐这种初始化 srand() 的策略。例如,在许多进程在同一秒内初始化的场景中,您的示例将在每个进程中生成相同的结果。这种行为可能不是用户所期望的。
          【解决方案13】:

          c++11 版本的 Jonathan Wright 投票率最高的帖子:

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

          【讨论】:

            【解决方案14】:

            只要您的程序仅在 Linux 上运行(并且您的程序是 ELF 可执行文件),就可以保证内核在 ELF 辅助向量中为您的进程提供唯一的随机种子。内核为您提供 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 以外的系统上实现了哪些辅助值。

            【讨论】:

              【解决方案15】:

              假设 srand() + rand() 的随机性足以满足您的目的,诀窍在于为 srand 选择最佳种子。 time(NULL) 是一个很好的起点,但是如果您在同一秒内启动多个程序实例,则会遇到问题。添加 pid(进程 ID)是一种改进,因为不同的实例将获得不同的 pid。我会将 pid 乘以一个因子以进一步传播它们。

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

              建议的初始化将是:

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

              在 Linux 机器中(至少在我测试过的树莓派中),你可以实现以下函数来获取 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;
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2023-03-19
                • 1970-01-01
                • 2020-12-11
                • 2018-03-06
                • 1970-01-01
                相关资源
                最近更新 更多