rand()、srand()、random()、/dev/urandom 與現代最佳實踐| 函數 | 標準 | 範圍 | 週期 | 執行緒安全 | 品質 |
|---|---|---|---|---|---|
rand() | C89 | 0 ~ RAND_MAX (≥32767) | ~231 (LCG) | 否 (全域狀態) | 低 (線性同餘法) |
srand(seed) | C89 | 設定種子 | — | 否 | — |
random() | POSIX.1-2001 | 0 ~ 231-1 | ~2128 (非線性反饋) | 否 (全域狀態) | 中 |
srandom(seed) | POSIX | 設定種子 | — | 否 | — |
rand_r(&seed) | POSIX.1-2008 | 0 ~ RAND_MAX | 同 rand | 是 (使用者提供種子) | 低 |
drand48() 族 | POSIX/XSI | [0.0, 1.0) double | 248 | 否 | 中 |
arc4random() | BSD/macOS | 32 位無符號 | ChaCha20 | 是 | 高 (加密級) |
getrandom() / /dev/urandom | Linux 3.17+ / POSIX | 任意位元組 | 系統熵池 | 是 | 高 (加密級) |
rand()/srand() 經典用法與陷阱
#include <stdlib.h>
#include <time.h>
int main() {
srand((unsigned)time(NULL)); // 以當前秒數為種子
for (int i = 0; i < 10; i++) {
printf("%d ", rand() % 100); // 0-99
}
return 0;
}
srand() 放在迴圈內
for (int i=0; i<10; i++) {
srand(time(NULL)); // 若迴圈在 1 秒內跑完,種子相同 → 產生相同序列
printf("%d ", rand());
}
srand((unsigned)time(NULL)); // main 開頭一次
for (int i=0; i<10; i++) printf("%d ", rand());
rand() % N 產生 0~N-1 (模數偏差)若 RAND_MAX=32767,N=100,則 0~67 出現 328 次,68~99 出現 327 次,機率不均。
int RandRange(int min, int max) { // [min, max]
int range = max - min + 1;
int limit = RAND_MAX - (RAND_MAX % range);
int r;
do { r = rand(); } while (r >= limit);
return min + r % range;
}
time(NULL) 秒級)同一秒內啟動多程式 → 相同序列。駭客可預測種子範圍暴力破解。
// 方案 A:getrandom (Linux 3.17+)
unsigned int seed;
getrandom(&seed, sizeof(seed), 0);
srand(seed);
// 方案 B:/dev/urandom (跨平台 POSIX)
int fd = open("/dev/urandom", O_RDONLY);
read(fd, &seed, sizeof(seed));
close(fd);
srand(seed);
// 方案 C:時間 + PID + 指標地址 + rdtsc (無系統呼叫)
unsigned int seed = (unsigned)(time(NULL) ^ (getpid()<<16) ^ (uintptr_t)&seed ^ __builtin_ia32_rdtsc());
srand(seed);
rand(),擁抱 C++11 <random> / C 系統熵源<random> (強型別、可組合、高品質)
#include <random>
#include <iostream>
int main() {
std::random_device rd; // 硬體熵源 (或偽隨機備援)
std::mt19937 gen(rd()); // 梅森旋轉演算法 (2^19937-1 週期)
std::uniform_int_distribution dist(1, 100); // 均勻分布 1-100
for (int i=0; i<10; i++) std::cout << dist(gen) << ' ';
// 正態分布、指數分布、伯努利分布... 內建支援
std::normal_distribution nd(0.0, 1.0);
std::cout << "\nNormal: " << nd(gen);
}
// random_util.h
#ifndef RANDOM_UTIL_H
#define RANDOM_UTIL_H
#include <stdint.h>
void RandomBytes(void *buf, size_t len); // 填充加密級隨機位元組
uint32_t RandomU32(void); // 32 位無符號
uint64_t RandomU64(void); // 64 位無符號
int RandomRange(int min, int max); // [min, max] 均勻
double RandomDouble(void); // [0.0, 1.0)
#endif
// random_util.c (Linux/macOS/BSD/Windows 通用)
#include "random_util.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
static int urandom_fd = -1;
void RandomBytes(void *buf, size_t len) {
// 嘗試 getrandom (Linux 3.17+)
#if defined(__linux__) && defined(SYS_getrandom)
if (syscall(SYS_getrandom, buf, len, 0) == (long)len) return;
#endif
// 回退 /dev/urandom
if (urandom_fd == -1) urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
if (urandom_fd != -1 && read(urandom_fd, buf, len) == (ssize_t)len) return;
// 最後回退:時間+PID+計數器 (非加密級,僅測試用)
static uint64_t counter = 0;
uint8_t *p = (uint8_t*)buf;
struct timeval tv; gettimeofday(&tv, NULL);
uint64_t seed = ((uint64_t)tv.tv_sec << 32) ^ tv.tv_usec ^ (getpid()<<16) ^ counter++;
for (size_t i=0; i> (i*8)) & 0xFF;
}
uint32_t RandomU32(void) { uint32_t v; RandomBytes(&v, 4); return v; }
uint64_t RandomU64(void) { uint64_t v; RandomBytes(&v, 8); return v; }
int RandomRange(int min, int max) {
uint32_t range = (uint32_t)(max - min + 1);
uint32_t limit = UINT32_MAX - (UINT32_MAX % range);
uint32_t r;
do { r = RandomU32(); } while (r >= limit);
return min + (int)(r % range);
}
double RandomDouble(void) {
return (double)RandomU32() / (UINT32_MAX + 1.0);
}
| 方法 | 耗時 | 每秒生成 | 適用場景 |
|---|---|---|---|
rand() | ~0.8 秒 | 1.25 億 | 簡單測試、教學、非關鍵路徑 |
random() | ~1.1 秒 | 9000 萬 | 需較長週期的 POSIX 程式 |
mt19937 (C++) | ~1.3 秒 | 7700 萬 | 科學計算、模擬、遊戲、生產級 C++ |
getrandom() 系統呼叫 | ~45 秒 | 220 萬 | 金鑰生成、Session ID、加密 Nonce |
/dev/urandom 讀取 | ~12 秒 | 830 萬 | 跨平台加密級、無 getrandom 系統 |
ChaCha20 (用戶態, arc4random) | ~2.0 秒 | 5000 萬 | BSD/macOS 預設、高效能加密級 |
策略:高頻非加密 → 用戶態 PRNG (mt19937, xoshiro256**, PCG) 定期從系統熵源重播種;低頻加密 → 直接 getrandom()//dev/urandom。
// 固定種子 → 確定性序列,利於單元測試回歸
std::mt19937 gen(12345); // 固定種子
// 或
srand(12345);
測試框架 (GoogleTest, Catch2) 可在 SetUp 重置種子,確保測試可重現。
rand()/srand() 是 C89 遺產,統計品質差、非執行緒安全、模數偏差、種子熵低。現代 C 專案應封裝系統熵源 (getrandom//dev/urandom) 做種子,搭配用戶態高品質 PRNG (xoshiro256**, PCG64, mt19937) 產生大量隨機數;C++ 專案直接使用 <random>。記住:加密用途務必用系統熵源或經認證的 CSPRNG,切勿自行實作。