【问题标题】:Generating random values without time.h生成没有 time.h 的随机值
【发布时间】:2018-05-11 04:54:37
【问题描述】:

我想在不使用 time.h 库的情况下重复生成随机数。我看到另一个关于使用的帖子

srand(getpid()); 

然而,这似乎对我不起作用 getpid 尚未声明。这是因为我错过了图书馆吗?如果是,我需要弄清楚如何在不使用我目前拥有的库之外的任何其他库的情况下随机生成数字。

#include <stdio.h>
#include <stdlib.h>


int main(void) {
    int minute, hour, day, month, year;
    srand(getpid());
    minute = rand() % (59 + 1 - 0) + 0;
    hour = rand() % (23 + 1 - 0) + 0;
    day = rand() % (31 + 1 - 1) + 1;
    month = rand() % (12 + 1 - 1) + 1;
    year = 2018;

    printf("Transferred successfully at %02d:%02d on %02d/%02d/%d\n", hour, 
    minute, day, month, year);

    return 0;
}

NB: 我只能使用库 &lt;stdio.h&gt;&lt;stdlib.h&gt;&lt;string.h&gt; — 严格的分配准则。

【问题讨论】:

  • 请注意,time()getpid() 都是非常(非常、非常)差的随机种子生成器。除了通过标准 I/O 访问特殊设备(请注意,&lt;stdio.h&gt; 是头文件,而不是库),列出的头文件中的标准 C 函数中没有任何好的随机性来源。您是否允许使用这些标头中的 POSIX 函数,或者您是否仅限于标准 C 函数? (生成好的随机数很难,生成好的随机种子也很难。)
  • 我不知道 POSIX 函数是什么。要求中没有提到它,所以我假设是这样。
  • 粗略地说,POSIX 是操作系统的“Unix 接口”。你可以从POSIX 2017 standard 中找到比你想知道的更多的东西——这是 POSIX 2008 的 2017 年更新。我的猜测是,如果你还没有意识到它们,你可能不会预计会使用它们。您的平台是 Linux 还是其他 Unix 变体,或者您使用的是 Windows 还是其他系统?
  • 我正在我的 Windows 笔记本电脑上运行 Ubuntu Bash 子系统。
  • 您可以使用任何东西作为随机种子。无论您使用什么种子,您都将创建一个 半随机 序列(在给定相同种子的情况下可重现)。因此,就此而言,您可以像 srand (time (NULL)); 一样使用srand (20180511);,这样做的好处是您现在知道种子是什么,并且可以在需要时重现该序列以进行调试。如果您需要用于加密目的的随机数,请查看自 Linux 内核 3.17 起可用的 getrandom,或从 /dev/urandom 读取。

标签: c random srand


【解决方案1】:

getpid 尚未声明。

不,因为您没有在声明它的地方包含&lt;unistd.h&gt; 标头(并且根据您的comment,您不能使用它,因为您只能使用&lt;stdlib.h&gt;&lt;string.h&gt;&lt;stdio.h&gt;)。

在这种情况下,我会使用类似的东西

#include <stdlib.h>
#include <stdio.h>

static int randomize_helper(FILE *in)
{
    unsigned int  seed;

    if (!in)
        return -1;

    if (fread(&seed, sizeof seed, 1, in) == 1) {
        fclose(in);
        srand(seed);
        return 0;
    }

    fclose(in);
    return -1;
}

static int randomize(void)
{
    if (!randomize_helper(fopen("/dev/urandom", "r")))
        return 0;
    if (!randomize_helper(fopen("/dev/arandom", "r")))
        return 0;
    if (!randomize_helper(fopen("/dev/random", "r")))
        return 0;

    /* Other randomness sources (binary format)? */

    /* No randomness sources found. */
    return -1;
}

还有一个简单的main() 来输出一些伪随机数:

int main(void)
{
    int i;

    if (randomize())
        fprintf(stderr, "Warning: Could not find any sources for randomness.\n");

    for (i = 0; i < 10; i++)
        printf("%d\n", rand());

    return EXIT_SUCCESS;
}

/dev/urandom/dev/random 字符设备可用于 Linux、FreeBSD、macOS、iOS、Solaris、NetBSD、Tru64 Unix 5.1B、AIX 5.2、HP-UX 11i v2 和 /dev/random/dev/arandom在 OpenBSD 5.1 及更高版本上。

像往常一样,Windows 似乎不提供任何此类随机源:Windows C 程序必须改用专有的 Microsoft 接口。

如果输入流为 NULL,或者无法从中读取 unsigned intrandomize_helper() 将返回非零值。如果它可以从中读取一个无符号整数,则它用于为您可以使用rand() 访问的标准伪随机数生成器提供种子(它返回一个介于 0 和RAND_MAX 之间的int,包括在内)。在所有情况下,randomize_helper() 都会关闭非 NULL 流。

您可以轻松地将其他二进制随机源添加到randomize()

如果randomize() 返回 0,rand() 应该返回伪随机数。否则,rand() 将返回相同的默认伪随机数序列。 (它们仍然是“随机的”,但每次运行程序时都会出现相同的序列。如果randomize()返回0,则每次运行程序时序列都会不同。)


大多数标准 C rand() 实现都是线性同余伪随机数生成器,通常参数选择不佳,因此速度较慢,而且不是很“随机”。

对于非加密工作,我喜欢实现 Xorshift 系列函数之一,最初由 George Marsaglia 编写。它们非常非常快,而且相当随机;他们通过了大多数统计随机性测试,例如diehard tests

在 OP 的情况下,可以使用 xorwow 生成器。根据当前的 C 标准,unsigned int 至少为 32 位,因此我们可以将其用作生成器类型。让我们看看替换标准 srand()/rand() 的实现是什么样的:

#include <stdlib.h>
#include <stdio.h>

/* The Xorwow PRNG state. This must not be initialized to all zeros. */
static unsigned int  prng_state[5] = { 1, 2, 3, 4, 5 };

/* The Xorwow is a 32-bit linear-feedback shift generator. */
#define  PRNG_MAX  4294967295u

unsigned int  prng(void)
{
    unsigned int  s, t;

    t = prng_state[3] & PRNG_MAX;
    t ^= t >> 2;
    t ^= t << 1;
    prng_state[3] = prng_state[2];
    prng_state[2] = prng_state[1];
    prng_state[1] = prng_state[0];
    s = prng_state[0] & PRNG_MAX;
    t ^= s;
    t ^= (s << 4) & PRNG_MAX;
    prng_state[0] = t;
    prng_state[4] = (prng_state[4] + 362437) & PRNG_MAX;
    return (t + prng_state[4]) & PRNG_MAX;
}

static int prng_randomize_from(FILE *in)
{
    size_t        have = 0, n;
    unsigned int  seed[5] = { 0, 0, 0, 0, 0 };

    if (!in)
        return -1;

    while (have < 5) {
        n = fread(seed + have, sizeof seed[0], 5 - have, in);
        if (n > 0 && ((seed[0] | seed[1] | seed[2] | seed[3] | seed[4]) & PRNG_MAX) != 0) {
            have += n;
        } else {
            fclose(in);
            return -1;
        }
    }

    fclose(in);
    prng_seed[0] = seed[0] & PRNG_MAX;
    prng_seed[1] = seed[1] & PRNG_MAX;
    prng_seed[2] = seed[2] & PRNG_MAX;
    prng_seed[3] = seed[3] & PRNG_MAX;
    prng_seed[4] = seed[4] & PRNG_MAX;

    /* Note: We might wish to "churn" the pseudorandom
             number generator state, to call prng()
             a few hundred or thousand times. For example:
       for (n = 0; n < 1000; n++) prng();
             This way, even if the seed has clear structure,
             for example only some low bits set, we start
             with a PRNG state with set and clear bits well
             distributed.
    */

    return 0;
}

int prng_randomize(void)
{
    if (!prng_randomize_from(fopen("/dev/urandom", "r")))
        return 0;
    if (!prng_randomize_from(fopen("/dev/arandom", "r")))
        return 0;
    if (!prng_randomize_from(fopen("/dev/random", "r")))
        return 0;
    /* Other sources? */
    /* No randomness sources found. */
    return -1;
}

上面对应的main()

int main(void)
{
    int  i;

    if (prng_randomize())
        fprintf(stderr, "Warning: No randomness sources found!\n");

    for (i = 0; i < 10; i++)
        printf("%u\n", prng());

    return EXIT_SUCCESS;
}

请注意,PRNG_MAX 有双重用途。一方面,它告诉prng() 可以返回的最大值——这是一个unsigned int,而不是像rand() 这样的int。另一方面,因为它必须是 232-1 = 4294967295,所以我们也用它来保证序列中生成下一个伪随机数时的临时结果保持 32 位。如果在 stdint.hinttypes.h 中声明的 uint32_t 类型可用,我们可以使用它并删除掩码 (&amp; PRNG_MAX)。

请注意,prng_randomize_from() 函数的编写使其仍然有效,即使随机源无法一次提供所有请求的字节,并返回 “短计数”。这在实践中是否会发生还有待商榷,但我更愿意确定。另请注意,如果该状态全为零,则它不接受该状态,因为这是 Xorwow PRNG 的一个被禁止的初始种子状态。

您显然可以在同一个程序中同时使用srand()/rand()prng()/prng_randomize()。我编写它们是为了让 Xorwow 生成器函数都以 prng 开头。

通常,我确实将 PRNG 实现放入头文件中,这样我就可以通过编写一个小测试程序轻松地对其进行测试(以验证它是否有效);而且我也可以通过切换到另一个头文件来切换 PRNG 实现。 (在某些情况下,我将 PRNG 状态放入一个结构中,并让调用者提供指向该状态的指针,以便可以同时使用任意数量的 PRNG,彼此独立。)

【讨论】:

  • 我只能使用库 以及 严格的分配准则。
  • @AngusRyan:那你不能使用getpid()。你在使用Linux,也许?如果是这样,您可以从 /dev/urandom 播种内置 PRNG。不过请注意,它是非常特定于 Linux 的。
  • BSD(macOS 和 Mac OS X)同时具有 /dev/random/dev/urandom,后者记录为:/dev/urandom 是对 Linux 的兼容性点头。在 Linux 上,如果熵池耗尽,/dev/urandom 将产生较低质量的输出,而/dev/random 将更喜欢阻塞并等待收集额外的熵。使用 Yarrow,这种选择和区分是不必要的,两种设备的行为相同。你可以使用任何一个。
  • @JonathanLeffler:我不知道(或者已经忘记了)。现在我知道;谢谢!
  • 有没有办法写一个我可以测试的例子。如何在上面的代码中实现 /dev/urandom?
【解决方案2】:

然而,这似乎对我不起作用 getpid 尚未声明。

那是因为您需要包含 getpid() 的标头:

   #include <sys/types.h>
   #include <unistd.h>

另一种选择是使用time() 播种(而不是getpid()):

   srand((unsigned int)time(NULL));

【讨论】:

  • 需要我不允许使用的 time.h 库。
  • 您需要以一种或其他方式播种。如果您很高兴每次运行程序时都重复相同的数字序列,那么您完全可以播种。其他选择是将您自己的种子作为参数传递并使用它(即,将该整数传递给srand)。如果您使用的是 POSIX 系统,则可以使用lrand48
  • @AngusRyan 它们是头文件,而不是库。我不知道为什么它对你的任务有限制。您可以考虑lrand48,因为它在stdlib.h 中声明。
【解决方案3】:

正如其他答案所指出的,您需要包含 unistd.h 标头。如果您不想这样做,请将getpid() 的声明放在main() 上方。在此处阅读getpid() 的手册页http://man7.org/linux/man-pages/man2/getpid.2.html

一种方法可能是

#include <stdio.h>
#include <stdlib.h>
pid_t getpid(void); /* put the declrataion of getpid(), if don't want to include the header */
int main(void) {

   /* .. some code .. */
   return 0;
} 

或者你可以使用time()

srand((unsigned int)time(NULL));

【讨论】:

  • 不能使用 pid_t 作为它的“未知名称类型”
  • 您的编译器将在编译时隐式导入pid_t,如果没有,则打开vim /usr/include/unistd.h 并找到pid_t 的定义并放入您的代码中。我认为你的任务没有任何意义。最好看srand()implimentation的源代码
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-07
  • 2023-03-11
  • 2019-08-23
  • 1970-01-01
  • 2021-06-01
相关资源
最近更新 更多