Linux C语言如何生成随机数?

99ANYc3cd6
预计阅读时长 21 分钟
位置: 首页 C语言 正文

伪随机数

要明白计算机生成的“随机数”实际上是伪随机数,它们是通过一个确定的算法(伪随机数生成器,PRNG)从一个初始值(称为种子,Seed)计算出来的序列。

linux c语言 随机数
(图片来源网络,侵删)

如果使用相同的种子启动,PRNG 将会生成完全相同的随机数序列。生成“真”随机数的关键在于提供一个不可预测的种子


rand()srand() (最简单,但不适用于安全场景)

这是 C 标准库 <stdlib.h> 提供的最基础、最简单的随机数生成方法。

rand() 函数

  • 原型: int rand(void);
  • 功能: 返回一个范围在 [0, RAND_MAX] 之间的伪随机整数。RAND_MAX 是一个宏,通常至少是 32767 (即 2^15 - 1)。
  • 问题: 如果你直接调用 rand(),它每次都会返回同一个序列的第一个数,这显然不是我们想要的。

srand() 函数

  • 原型: void srand(unsigned int seed);
  • 功能: 用来初始化(或“播种”)伪随机数生成器,你需要给它一个不同的种子,才能让 rand() 产生不同的序列。

如何选择一个好的种子?

最常见的方法是使用当前时间作为种子,因为每次运行程序的时间都不同。

  • 头文件: <time.h>
  • 函数: time(NULL) 返回自纪元(1970年1月1日)以来的秒数。

完整示例:生成 10 个 1 到 100 之间的随机数

#include <stdio.h>
#include <stdlib.h> // rand(), srand(), RAND_MAX
#include <time.h>   // time()
int main() {
    // 1. 使用当前时间作为随机数生成器的种子
    // 只需要调用一次,通常放在程序开始处
    srand((unsigned int)time(NULL));
    printf("Generating 10 random numbers between 1 and 100:\n");
    // 2. 循环生成随机数
    for (int i = 0; i < 10; i++) {
        // rand() % 100 生成 0 到 99 之间的数
        // 加 1 后,范围变为 1 到 100
        int random_number = (rand() % 100) + 1;
        printf("%d ", random_number);
    }
    printf("\n");
    return 0;
}

rand() 的缺点

  • 质量不高: 生成的随机数序列可能不够随机,存在一定的规律性。
  • 不安全: 它是可预测的。绝对不能用于生成密码、密钥、会话令牌等安全相关的随机数,攻击者可以预测出下一个随机数是什么。

random()srandom() (比 rand() 更好)

这是 GNU C 库 (glibc) 提供的一个扩展,通常比 rand() 产生质量更高的随机数。

linux c语言 随机数
(图片来源网络,侵删)

random() 函数

  • 原型: long int random(void);
  • 功能: 返回一个范围在 [0, RAND_MAX_MAX] 之间的长整型伪随机数。RAND_MAX_MAX 通常比 RAND_MAX 大得多,能提供更大的范围和更好的统计特性。

srandom() 函数

  • 原型: void srandom(unsigned int seed);
  • 功能: 与 srand() 类似,用于初始化 random() 生成器。

完整示例:生成 10 个 1 到 1000 之间的随机数

#include <stdio.h>
#include <stdlib.h> // random(), srandom()
#include <time.h>   // time()
int main() {
    // 1. 使用当前时间作为种子
    srandom((unsigned int)time(NULL));
    printf("Generating 10 random numbers between 1 and 1000:\n");
    // 2. 循环生成随机数
    for (int i = 0; i < 10; i++) {
        // random() % 1000 生成 0 到 999 之间的数
        // 加 1 后,范围变为 1 到 1000
        long int random_number = (random() % 1000) + 1;
        printf("%ld ", random_number);
    }
    printf("\n");
    return 0;
}

random() 的优点

  • 质量更高: 通常比 rand() 产生的随机数序列更均匀,统计特性更好。
  • 范围更大: 返回 long int,范围更广。

random() 的缺点

  • 仍然不安全: 和 rand() 一样,它是伪随机的,可以被预测。同样不能用于安全目的

/dev/urandom (推荐用于安全和高质量场景)

在 Linux 系统中,有一个特殊的设备文件 /dev/urandom,它是一个伪随机数生成器,但它的熵池(entropy pool)是从系统环境噪声(如中断时间、鼠标移动、键盘敲击等)中收集的,因此它被认为是密码学安全的

如何使用?

你需要以二进制读取的方式打开 /dev/urandom 文件,然后从中读取字节。

示例:生成一个 32 位的随机整数

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>   // open()
#include <unistd.h>  // read()
#include <sys/random.h> // getrandom() (更现代的方式)
// 方法一:直接读取 /dev/urandom
int get_random_number_from_dev_urandom() {
    int random_fd = open("/dev/urandom", O_RDONLY);
    if (random_fd == -1) {
        perror("Error opening /dev/urandom");
        exit(EXIT_FAILURE);
    }
    int random_value;
    // read() 从文件描述符中读取数据
    // sizeof(random_value) 是要读取的字节数
    // &random_value 是存储数据的缓冲区
    if (read(random_fd, &random_value, sizeof(random_value)) != sizeof(random_value)) {
        perror("Error reading from /dev/urandom");
        close(random_fd);
        exit(EXIT_FAILURE);
    }
    close(random_fd);
    return random_value;
}
int main() {
    printf("A random number from /dev/urandom: %d\n", get_random_number_from_dev_urandom());
    return 0;
}

/dev/urandom 的优点

  • 密码学安全: 适用于生成密钥、 salts、会话令牌等。
  • 高质量: 随机数质量非常高。
  • 简单易用: 在 Linux 环境下,直接读文件即可。

/dev/urandom 的缺点

  • 平台相关: 这是 Linux/Unix 系统的特性,在 Windows 或其他操作系统上不可用。
  • 需要文件 I/O: 相比库函数,多了一步打开和关闭文件的操作。

getrandom() (现代 Linux 推荐方式)

getrandom() 是一个相对较新的 Linux 系统调用 (从 Linux 3.17 开始引入),它被认为是比读取 /dev/urandom 更好的方式,因为它更直接、更高效。

如何使用?

  • 头文件: <sys/random.h>
  • 原型: ssize_t getrandom(void *buf, size_t buflen, unsigned int flags);

参数说明

  • buf: 存储随机数据的缓冲区。
  • buflen: 要读取的字节数。
  • flags: 通常设置为 0,也可以使用 GRND_RANDOMGRND_NONBLOCK,但 0 在大多数情况下就足够了。

示例:生成一个 32 位的随机整数

#include <stdio.h>
#include <stdlib.h>
#include <sys/random.h> // getrandom()
int main() {
    int random_value;
    ssize_t bytes_read;
    // getrandom() 会阻塞,直到有足够的熵可用
    // sizeof(random_value) 是要读取的字节数
    // &random_value 是存储数据的缓冲区
    // 0 表示默认标志
    bytes_read = getrandom(&random_value, sizeof(random_value), 0);
    if (bytes_read != sizeof(random_value)) {
        perror("Error getting random number with getrandom()");
        exit(EXIT_FAILURE);
    }
    printf("A random number from getrandom(): %d\n", random_value);
    return 0;
}

getrandom() 的优点

  • 更现代、更高效: 是专门为获取随机数据设计的系统调用,比文件 I/O 更轻量。
  • 密码学安全: 与 /dev/urandom 一样安全。
  • 推荐用法: 在现代 Linux 应用程序中,这是获取随机数的首选方法。

getrandom() 的缺点

  • 平台相关: 同样是 Linux 特有的系统调用。

总结与选择建议

方法 头文件 优点 缺点 推荐场景
rand()/srand() <stdlib.h> C标准,所有平台都支持 质量差,不安全 教学、非关键性的随机性需求(如游戏中的简单随机事件)
random()/srandom() <stdlib.h> 质量比 rand() 好,范围大 仍然不安全 对随机数质量有一定要求,但不涉及安全的场景(如科学模拟)
/dev/urandom <fcntl.h>, <unistd.h> 密码学安全,质量高 平台相关,需要文件I/O Linux下的安全应用(加密、密钥、令牌)
getrandom() <sys/random.h> 密码学安全,现代,高效 平台相关 现代Linux应用的首选,特别是需要安全随机数的场景

如何选择?

  1. 只是随便用用,比如练习写个小游戏:用 rand()srand((unsigned int)time(NULL)) 就足够了。
  2. 需要更好的随机数质量,但不用安全:用 random()srandom()
  3. 在 Linux 上开发,需要安全的随机数(例如生成 API 密钥、盐值等):优先使用 getrandom(),如果因为兼容性原因无法使用 getrandom(),那么读取 /dev/urandom 是一个非常可靠的选择。
linux c语言 随机数
(图片来源网络,侵删)
-- 展开阅读全文 --
头像
织梦文章属性字母简写有何规律或作用?
« 上一篇 03-15
网站打开为何显示织梦标志?
下一篇 » 03-15

相关文章

取消
微信二维码
支付宝二维码

目录[+]