C言語でゲームやシミュレーションを作ると、実行のたびに乱数が同じ並びになって戸惑うことがあります。
これはバグではなく仕様です。
この記事では、そうなる理由と、srandとtimeを使って毎回異なる乱数列にする方法を、初心者の方にも分かるように丁寧に説明します。
乱数の「種」(seed)の理解が鍵です。
乱数が毎回同じ理由
randは疑似乱数
C言語のrand関数は「疑似乱数」を生成します。
つまり、真の偶然ではなく、ある計算手順に従って並びを作る仕組みです。
この手順は決定的で、計算の出発点となるseed(種)が同じなら同じ並びが再現されます。
疑似乱数は「同じ種→同じ乱数列」という性質を持つため、再現性が高い一方で、種の選び方が重要になります。
なお、暗号用途にはrandは不向きであることも覚えておくと安心です。
種(seed)が同じなら結果は同じ
seedは乱数生成器の初期状態を決める値です。
同じseedで初期化すれば、randが返す値の列は常に同じになります。
これはテストやデバッグで非常に便利です。
例えばsrand(1234)で初期化したプログラムは、毎回まったく同じ乱数列を返します。
srandを呼ばないと同じ結果が出る
srandを一度も呼ばない場合、実装依存ですが多くの処理系は固定の初期種を使います。
そのため、プログラムを再実行しても乱数列が毎回同じになります。
以下はsrandを呼ばない例です。
// srandを呼ばずにrand()を使う例
// コンパイル: gcc no_seed.c -o no_seed
// 実行すると、毎回同じ並びの数字が表示される(処理系依存で値は異なる)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    for (int i = 0; i < 5; ++i) {
        // 0〜RAND_MAXの整数を返す
        printf("%d\n", rand());
    }
    return 0;
}実行結果の例(ある環境の一例。あなたの環境では数値そのものは異なることがあります):
1804289383
846930887
1681692777
1714636915
1957747793もう一度同じプログラムを実行すると、同じ環境では次のように同じ並びが得られます。
1804289383
846930887
1681692777
1714636915
1957747793乱数の種を設定するsrand()の基本
seed(種)を渡して初期化する
srandは乱数の種を設定する関数です。
ヘッダ#include <stdlib.h>をインクルードし、unsigned intの値を渡して初期化します。
同じ種なら同じ乱数列になる点を確認してみましょう。
// 固定シードで初期化する例: srand(1234)
// コンパイル: gcc fixed_seed.c -o fixed_seed
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    unsigned int seed = 1234;        // 任意の固定シード
    srand(seed);                      // 乱数の種を設定
    printf("seed=%u\n", seed);
    for (int i = 0; i < 5; ++i) {
        printf("%d\n", rand());       // 同じseedなら同じ並び
    }
    return 0;
}seed=1234
822569775
2137449171
177904250
1230211710
280885587同じプログラムをもう一度実行してもまったく同じ出力になります。
呼ぶのはプログラム開始時に1回だけ
srandは原則としてプログラム開始時に1回だけ呼びます。
繰り返し呼ぶと、乱数生成器がその都度リセットされ、偏った結果や同じ数の繰り返しになりがちです。
次の悪い例では、ループのたびにsrandし直してしまっています。
// 悪い例: ループ内で毎回srandを呼ぶと、列が毎回リセットされる
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
    for (int i = 0; i < 5; ++i) {
        // 悪手: 毎回初期化してしまう。time(NULL)が同じ秒だと同じ値になる
        srand((unsigned int)time(NULL));
        printf("%d\n", rand()); // 毎回「最初の1個目」だけが出る
    }
    return 0;
}出力の例(高速に実行され同じ秒のうちだと):
1323456789
1323456789
1323456789
1323456789
1323456789ループ内でsrandを呼ぶのは避けましょう。
正しくは「一度だけ初期化→その後はrandを繰り返し呼ぶ」です。
time()で種を変えて毎回違う乱数にする
time(NULL)を種に使う理由
毎回違う乱数列がほしいなら、実行ごとに変化する値をseedに使うのが簡単です。
標準ライブラリ#include <time.h>のtime(NULL)は「UNIXエポックからの経過秒」を返すため、プログラムを別のタイミングで起動すれば値が変わります。
移植性が高く、依存関係も少ないため、初心者向けの定番手法です。
なおtimeは秒単位なので、同じ秒のうちに起動すると同じ列になる点には注意します。
書き方: srand((unsigned int)time(NULL))
timeの戻り値time_tは実装によって幅が異なるため、(unsigned int)time(NULL)にキャストしてsrandへ渡すのが一般的です。
// 実行ごとに異なる乱数列にする: time(NULL)で種を与える
// コンパイル: gcc time_seed.c -o time_seed
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
    unsigned int seed = (unsigned int)time(NULL); // 秒単位の現在時刻
    srand(seed);                                   // 一度だけ初期化
    printf("seed=%u\n", seed);
    for (int i = 0; i < 5; ++i) {
        printf("%d\n", rand());
    }
    return 0;
}実行結果の例1:
seed=1734240000
1033096050
1904111251
1453483691
408594243
13012909711秒以上待ってから再実行すると、別のシード→別の並びになります。
seed=1734240002
1702067438
1280259972
1954344569
1743080471
1056096541初心者がハマりやすい注意点
ループ内でsrandを毎回呼ばない
最重要の落とし穴は、ループ内でsrandを繰り返し呼ぶことです。
これは乱数生成器を毎回初期化してしまい、列の先頭要素ばかりが出続けます。
初期化はプログラム開始時か、明確な意図がある時に1回だけにしましょう。
同じ秒の起動は同じ列になる
time(NULL)は秒単位のため、同じ秒に起動したプロセスは同じ種になり得ます。
ゲームのタイトル画面など、連打で何度も起動する状況では同じ乱数列に遭遇する可能性があります。
回避策としては、1秒以上待つ、乱数を開始前に数回捨て引きする、あるいはtime(NULL)にプロセスIDを足すなどがあります。
// 実用上の工夫例: 同じ秒でもズレやすくする
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include <process.h> // _getpid
#define GETPID() _getpid()
#else
#include <unistd.h>  // getpid
#define GETPID() getpid()
#endif
int main(void) {
    unsigned int seed = (unsigned int)time(NULL) ^ (unsigned int)GETPID();
    srand(seed);
    printf("seed=%u\n", seed);
    // 乱数を数個「捨て引き」して初期の相関を弱めることもある
    for (int i = 0; i < 4; ++i) (void)rand();
    for (int i = 0; i < 5; ++i) {
        printf("%d\n", rand());
    }
    return 0;
}seed=1734240051
1863778623
1451405927
836309924
1857918521
1414740252この方法でも暗号学的な強度は得られませんが、実用上の重複を減らす助けになります。
テストは固定シード(srand(1234))で再現性
バグ報告やユニットテストでは再現性が重要です。
固定シードを使えば、同じ入力で常に同じ結果を得られます。
実行時引数でシードを切り替えると、テストと本番を簡単に使い分けできます。
// 実行時引数でseedを切り替えるテンプレート
// 使い方: ./prog           → 時刻ベースのseed
//        ./prog 1234      → 固定seed=1234で再現性確保
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[]) {
    unsigned int seed;
    if (argc >= 2) {
        seed = (unsigned int)strtoul(argv[1], NULL, 10);
    } else {
        seed = (unsigned int)time(NULL);
    }
    srand(seed);
    printf("seed=%u\n", seed);
    for (int i = 0; i < 5; ++i) {
        printf("%d\n", rand());
    }
    return 0;
}実行結果の例1(引数なし):
seed=1734240067
1278103912
2000229472
1238515987
1906738325
1671865969実行結果の例2(固定シード):
seed=1234
822569775
2137449171
177904250
1230211710
280885587randを使う前に一度だけsrandを呼ぶ
基本の順序は「srandを最初に一度→randを必要回数呼ぶ」です。
プロジェクトの雛形として、次のように冒頭で初期化しておくと安全です。
// 雛形: mainの冒頭で一度だけ初期化
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
    srand((unsigned int)time(NULL)); // ここで一度だけ初期化
    // 以降は自由にrand()を使う
    for (int i = 0; i < 3; ++i) {
        printf("%d\n", rand());
    }
    return 0;
}1113438942
559509220
1648864588この雛形を守るだけで、多くの「毎回同じになる」トラブルは避けられます。
以下の表に、代表的なシードの与え方と特徴をまとめます。
| シードの設定方法 | 典型的な用途 | 再現性 | 注意点 | 
|---|---|---|---|
| srandを呼ばない | デモや学習初期 | あり(毎回同じ) | 実運用では不向き | 
| srand(固定値) | テスト、デバッグ | あり(完全再現) | 本番では変化しない | 
| srand((unsigned int)time(NULL)) | 一般的な実行 | なし(毎回変化) | 同じ秒だと同じ列 | 
目的に応じて「固定」「時刻」のどちらを選ぶかを決めると、テストと本番の両立がしやすくなります。
まとめ
本記事では、C言語のrandが疑似乱数であり、種が同じなら同じ結果になること、そしてsrandでプログラム開始時に一度だけ初期化すべき理由を解説しました。
毎回異なる乱数列が必要なときはtime(NULL)を用いて実行時に変わる種を設定し、テストではsrand(固定値)で再現性を確保します。
加えて、ループ内でsrandを呼ばないこと、timeは秒単位であるため同一秒起動だと同じ列になることにも注意が必要です。
これらの基本を押さえれば、乱数の挙動で迷うことはぐっと減ります。
最後にもう一度だけ強調します。
「最初に一度srand、あとはrand」。
この型を体で覚えておきましょう。

 
			