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

(图片来源网络,侵删)
如果使用相同的种子启动,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() 产生质量更高的随机数。

(图片来源网络,侵删)
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_RANDOM或GRND_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应用的首选,特别是需要安全随机数的场景 |
如何选择?
- 只是随便用用,比如练习写个小游戏:用
rand()和srand((unsigned int)time(NULL))就足够了。 - 需要更好的随机数质量,但不用安全:用
random()和srandom()。 - 在 Linux 上开发,需要安全的随机数(例如生成 API 密钥、盐值等):优先使用
getrandom(),如果因为兼容性原因无法使用getrandom(),那么读取/dev/urandom是一个非常可靠的选择。

(图片来源网络,侵删)
