首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言隨機數生成完全指南:rand()、srand()、random()、/dev/urandom 與現代最佳實踐

     編輯:關於C語言

核心函數速覽

函數標準範圍週期執行緒安全品質
rand()C890 ~ RAND_MAX (≥32767)~231 (LCG)否 (全域狀態)低 (線性同餘法)
srand(seed)C89設定種子—否—
random()POSIX.1-20010 ~ 231-1~2128 (非線性反饋)否 (全域狀態)中
srandom(seed)POSIX設定種子—否—
rand_r(&seed)POSIX.1-20080 ~ RAND_MAX同 rand是 (使用者提供種子)低
drand48() 族POSIX/XSI[0.0, 1.0) double248否中
arc4random()BSD/macOS32 位無符號ChaCha20是高 (加密級)
getrandom() / /dev/urandomLinux 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;
}

常見錯誤

❌ 錯誤 1: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());

❌ 錯誤 2: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;
}

❌ 錯誤 3:種子來源單一 (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 系統熵源

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

純 C 專案:系統熵源 + 簡易包裝


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

效能比較 (單核 3.0 GHz, 1 億次生成)

方法耗時每秒生成適用場景
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,切勿自行實作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved