閉じる

【C言語】sleep関数の使い方【秒・ミリ秒・Windows対応】

プログラムで一定時間だけ処理を止めたい場面は意外と多いです。

C言語ではsleep関数などを使うことで、秒やミリ秒単位で簡単に待機処理を実現できます。

ただし、UNIX系とWindowsで仕様が異なり、間違えると動かなかったり精度が出なかったりします。

本記事ではC言語でのsleepの正しい使い方と秒・ミリ秒・Windowsを含めた実践的な待機方法をわかりやすく解説します。

C言語のsleep関数とは

sleep関数の概要と用途

sleep関数は、プログラムの実行を一定時間だけ一時停止するための関数です。

最も代表的な形は、UNIX系(POSIX)環境で使われるunsigned int sleep(unsigned int seconds)で、指定した秒数だけ現在のスレッド(プロセス)を停止させます。

一般的に、次のような用途で使われます。

  • ログの出力間隔を一定時間ごとにしたい場合
  • ゲームやツールで、画面更新の間隔をあけたい場合
  • ネットワーク接続のリトライで、一定時間待ってから再試行したい場合
  • センサーやハードウェア制御で、決められた周期で処理したい場合

このようにsleepは「何もしない時間」を明示的に作るための基本機能です。

sleep関数で処理を一時停止するメリット

sleep関数で処理を一時停止することには、いくつかのメリットがあります。

まずCPU資源の節約です。

待機したいときに、ループでひたすら現在時刻を監視するような方法(いわゆるbusy wait)を行うと、その間CPUを占有してしまいます。

sleepを使えば、OSがスレッドを休ませてくれるため、他のプロセスにCPUを譲ることができます。

またプログラムの読みやすさと保守性の向上という利点もあります。

例えば、ネットワークの再接続処理をする場合、「エラー → すぐ再試行」を延々と繰り返すと、ログが大量に出てしまったり、相手サーバーに負荷をかけたりします。

sleepを使って「1秒待つ」「5秒待つ」と明示しておけば、意図がコードから読み取りやすくなります。

最後に時間の制御がしやすくなる点も重要です。

ゲームループや周期処理など、ある程度の時間間隔を保ちたい処理で、sleepを組み合わせることで、処理全体のペースを調整することができます。

C言語の標準ライブラリとの関係

ここで注意したいのが、sleep関数はC言語の「厳密な意味での標準ライブラリ」には含まれていないという点です。

sleepはPOSIX標準(主にUNIX系OS向けの仕様)で定義されており、C標準規格(ISO C)に含まれる関数ではありません。

そのため、環境によって次のような違いがあります。

  • LinuxやmacOSなどのPOSIX環境では、#include <unistd.h>でsleepが利用可能
  • Windowsではsleepはなく、代わりにSleep(WinAPI)を使う必要がある
  • 一部処理系では独自拡張としてsleepを提供している場合もあるが、移植性は保証されない

移植性の高いコードを書きたい場合、POSIX系とWindowsで分けて実装するか、ラッパー関数を自作する必要があります。

後半の「クロスプラットフォーム対応」で具体的な設計例を説明します。

秒単位でのsleep関数の使い方

sleep関数の基本的な書き方

POSIX環境(Linux, macOS, 一部の組み込みUNIXなど)で、最も基本的な使い方は次のようになります。

C言語
#include <stdio.h>    // printfの宣言
#include <unistd.h>   // sleepの宣言 (POSIX)

int main(void) {
    printf("処理開始\n");

    // 3秒間だけ処理を停止する
    sleep(3);

    printf("3秒経過しました\n");
    return 0;
}

このコードではsleep(3)によって、呼び出し元のスレッドが約3秒間停止します。

秒単位でシンプルに待ちたい場合は、基本的にこの形式だけ覚えておけば十分です。

unsigned int sleep(unsigned int seconds)の仕様

POSIXでのsleep関数のプロトタイプは、一般に次のように定義されています。

C言語
unsigned int sleep(unsigned int seconds);

ここでのポイントを整理します。

  • 引数secondsは待機したい秒数を指定します。型はunsigned intなので、0以上の整数しか指定できません。
  • 戻り値もunsigned intです。これは「指定した秒数のうち、実際には経過しなかった秒数」を表す仕様です。
  • 実際の停止時間はOSのスケジューリングに依存するため、指定した秒数ちょうどになるとは限りません

たとえばsleep(10)を呼んだ場合、通常は約10秒後に処理が再開され、戻り値は0となります。

しかし、シグナルで中断された場合などには、残り秒数が返ってくることがあります。

sleep関数の戻り値と注意点

sleep関数で特に注意しておきたい点を詳しく見ていきます。

戻り値の意味

POSIX仕様では、sleepの戻り値は次のように定義されています。

  • 0: 指定した時間がすべて経過してから戻った(正常終了)
  • 0以外の値: 何らかの理由で、指定時間よりも早く戻された。この値は「残り秒数」を表す。

代表的にはシグナルによってsleepが中断された場合に、残り時間が返されます。

ただし、一般的な簡単なプログラムでは、戻り値を0として扱ってしまうことも多いです。

C言語
#include <stdio.h>
#include <unistd.h>

int main(void) {
    unsigned int sec = 10;
    printf("%u秒待機します\n", sec);

    unsigned int remain = sleep(sec);

    if (remain == 0) {
        printf("指定した%u秒が経過しました\n", sec);
    } else {
        printf("途中で中断されました。残り%u秒でした\n", remain);
    }
    return 0;
}

指定時間ちょうどではない可能性

sleepは「最低でもこのくらいの時間は寝る」保証ではなく、「およそこのくらい」という性質を持つと理解しておくと安全です。

OSのスケジューラやシステム負荷によって、実際の停止時間は前後します。

そのため、厳密なタイミング制御(リアルタイム性が必要)にはsleepは向きません

そのような用途では、リアルタイムOSや専用API、ハードウェアタイマなどを検討する必要があります。

ここでは、sleepを用いたシンプルなカウントダウンプログラムを示します。

1秒ごとに残り秒数を表示しながら、0になるまで待機します。

C言語
#include <stdio.h>
#include <unistd.h>  // sleep

int main(void) {
    int i;
    int count = 5;   // 5秒カウントダウン

    printf("カウントダウンを開始します\n");

    for (i = count; i > 0; --i) {
        printf("残り %d 秒...\n", i);

        // 1秒待機
        sleep(1);
    }

    printf("カウントダウン終了\n");
    return 0;
}

想定される実行結果は次のようになります。

カウントダウンを開始します
残り 5 秒...
残り 4 秒...
残り 3 秒...
残り 2 秒...
残り 1 秒...
カウントダウン終了

ミリ秒で待機する方法

C言語でミリ秒待機するには

ここまでのsleepは秒単位の待機でした。

しかし実際には「0.5秒待ちたい」「100ミリ秒ごとに更新したい」といったより細かい単位の待機が必要になることが多くあります。

ところがPOSIXのsleepは秒単位でしか指定できないため、ミリ秒待機には次のような別の手段を使います。

  • usleep関数: マイクロ秒単位(1/1000000秒)で待機
  • nanosleep関数: ナノ秒単位(1/1000000000秒)まで指定可能

いずれもPOSIX由来の関数であり、C標準規格には含まれません。

そのため主にUNIX系環境で利用できると考えてください。

usleep関数の使い方

usleepは、マイクロ秒単位で待機時間を指定できる関数です。

プロトタイプは以下のようになります(環境により若干異なる場合があります)。

C言語
int usleep(useconds_t usec);

ここでusecは待機したい時間をマイクロ秒(μs)で表した値です。

1ミリ秒は1000マイクロ秒なので、ミリ秒で待ちたい場合は「ミリ秒 × 1000」を指定します。

C言語
#include <stdio.h>
#include <unistd.h>  // usleep

int main(void) {
    printf("100ミリ秒待機します\n");

    // 100ミリ秒 = 100 * 1000マイクロ秒
    usleep(100 * 1000);

    printf("100ミリ秒が経過しました\n");
    return 0;
}

120ミリ秒待機したいならusleep(120 * 1000)、1秒待機したいならusleep(1000 * 1000)という計算になります。

なおusleepは、環境によっては非推奨(deprecated)扱いになっていることがあります。

新しいコードでは、より柔軟なnanosleepを使うことが推奨される場合もあります。

nanosleep関数の使い方

nanosleepは、ナノ秒単位で待機時間を指定できるより高機能な関数です。

プロトタイプは通常次のようになっています。

C言語
#include <time.h>

int nanosleep(const struct timespec *req, struct timespec *rem);

ここで使われるstruct timespecは、秒とナノ秒の2つの要素を持つ構造体です。

C言語
struct timespec {
    time_t tv_sec;   // 秒
    long   tv_nsec;  // ナノ秒 (0〜999,999,999)
};

ミリ秒で待機したい場合は、次のように変換します。

  • 待機したい時間をミリ秒(cst-code>ms)とする
  • 秒部分: tv_sec = ms / 1000
  • ナノ秒部分: tv_nsec = (ms % 1000) * 1000000L

具体例として、150ミリ秒待機するプログラムを示します。

C言語
#include <stdio.h>
#include <time.h>    // nanosleep, struct timespec

// ミリ秒単位で待機する関数 (POSIX環境用)
void msleep(long msec) {
    struct timespec req;

    // 秒とナノ秒に分解
    req.tv_sec  = msec / 1000;
    req.tv_nsec = (msec % 1000) * 1000000L;  // 1ミリ秒 = 1000000ナノ秒

    // 指定時間だけスリープ
    nanosleep(&req, NULL);
}

int main(void) {
    printf("150ミリ秒待機します\n");
    msleep(150);
    printf("150ミリ秒が経過しました\n");

    return 0;
}

nanosleepは、sleepやusleepと同様にシグナルで中断されることがあります。

この場合、2番目の引数rem残り時間として設定されるため、必要に応じて再度nanosleepを呼び出して残り時間だけ待つ、といった制御も可能です。

busy wait(ループ待機)を避けるべき理由

ミリ秒単位の待機を実現しようとして、次のような誤ったアプローチを取ってしまうことがあります。

C言語
// 悪い例: 時間をチェックし続けて待機するbusy wait
#include <time.h>

void bad_wait_ms(long msec) {
    clock_t start = clock();
    clock_t end = start + (clock_t)(msec * (CLOCKS_PER_SEC / 1000.0));

    // 条件を満たすまでループし続ける
    while (clock() < end) {
        // 何もしない
    }
}

このようなbusy wait(ビジーウェイト)には、次のような問題があります。

  • ループ中ずっとCPUを占有するため、CPU使用率が100%近くになる
  • ノートPCなどでは、バッテリー消費や発熱が増える
  • 他のプロセスやスレッドの動作に悪影響を与える
  • 実際の待機時間の精度も、必ずしも高くない

そのため、待機したいときはsleep/usleep/nanosleep/Sleep(Windows)など、OSに制御を返すAPIを使うべきです。

busy waitは、どうしてもリアルタイム性が求められる特殊な状況や、専用のハードウェアタイマと組み合わせる場合など、非常に限定されたケースのみで検討してください。

Windowsでのsleep関数の対応方法

Windows環境とPOSIX系sleepの違い

Windows環境では、unistd.hやPOSIXのsleepは通常利用できません。

その代わりWinAPIとしてSleep関数が提供されています。

主な違いを表にまとめると、次のようになります。

項目POSIX系 (Linuxなど)Windows
関数名sleepSleep
ヘッダ#include <unistd.h>#include <windows.h>
単位ミリ秒
戻り値残り秒数(unsigned int)なし(void)

このようにWindowsのSleepはミリ秒単位で待機できるため、ミリ秒待機をしたい場合には扱いやすいです。

一方で関数名とヘッダが異なるため、同じソースコードをそのままPOSIXとWindowsでコンパイルすることはできません

Sleep関数(WinAPI)の使い方

WindowsのSleep関数は、次のようなプロトタイプを持ちます。

C言語
VOID Sleep(DWORD dwMilliseconds);

引数dwMillisecondsに、待機したい時間をミリ秒単位で指定します。

1000ミリ秒 = 1秒です。

C言語
#include <stdio.h>
#include <windows.h>  // Sleep

int main(void) {
    printf("1秒(1000ミリ秒)だけ待機します\n");

    Sleep(1000);  // 1000ミリ秒 = 1秒

    printf("1秒が経過しました\n");
    return 0;
}

0.5秒だけ待機したい場合はSleep(500)とします。

Windowsでは、ミリ秒待機を標準でサポートしているため、POSIXのようにusleepやnanosleepを使わなくても細かい時間指定が簡単にできます。

Sleep関数のヘッダとリンク設定

WindowsでSleepを使うためには、必ず#include <windows.h>を記述する必要があります。

これを忘れると、コンパイル時に「宣言されていない関数」としてエラーになります。

C言語
#include <windows.h>  // Sleepを使うために必要

リンク設定については、通常のWindowsアプリケーションやコンソールアプリケーションであれば、特別なライブラリ指定をしなくてもSleepは使えます。

Visual StudioやMinGWなどの一般的な開発環境では、標準のWinAPIライブラリが自動的にリンクされるからです。

ただし、非常に特殊なビルド環境や、WinAPIを最小構成で使うような設定をしている場合には、明示的にライブラリ(kernel32.libなど)をリンクする必要が生じるケースもあります。

一般的な入門〜中級者の環境では、そこまで意識しなくても問題ありません。

クロスプラットフォーム対応の待機処理設計

最後にPOSIXとWindowsの両方で動作する「移植性の高い待機処理」をどう設計するかを見ていきます。

ポイントは次の2つです。

  1. 条件付きコンパイル(#ifdefなど)を使って、OSごとに適切なAPIを呼び分ける
  2. 共通のラッパー関数を用意して、アプリ側のコードからはOS差を意識しなくて済むようにする

ここでは、ミリ秒単位で待機するmsleepという関数を自作し、WindowsとPOSIXでそれぞれ異なる実装を行う例を示します。

C言語
#include <stdio.h>

#ifdef _WIN32
    // Windows環境の場合
    #include <windows.h>

    void msleep(unsigned int msec) {
        // WindowsのSleepはミリ秒単位
        Sleep(msec);
    }

#else
    // POSIX環境 (Linux, macOSなど) の場合
    #include <time.h>

    void msleep(unsigned int msec) {
        struct timespec req;

        req.tv_sec  = msec / 1000;                 // 秒
        req.tv_nsec = (msec % 1000) * 1000000L;    // ミリ秒 → ナノ秒

        nanosleep(&req, NULL);
    }

#endif

int main(void) {
    printf("500ミリ秒待機します\n");

    // OSを意識せずにmsleepを呼び出せる
    msleep(500);

    printf("500ミリ秒が経過しました\n");
    return 0;
}

実行結果(WindowsでもLinuxでも、おおよそ同じ表示になります)。

500ミリ秒待機します
500ミリ秒が経過しました

このように共通インターフェース(msleep)を定義し、内部でOSごとのAPIを呼び分けることで、アプリケーションコードの可読性と移植性が大きく向上します。

今後別のOSに対応したくなっても、msleepの実装を増やすだけで済むため、保守もしやすくなります。

まとめ

sleep関連の関数は、C言語で時間制御を行ううえで欠かせない基本要素です。

UNIX系ではsleepが秒単位、usleepnanosleepがより細かい単位での待機を実現し、WindowsではSleepがミリ秒単位の待機を提供します。

busy waitのようにCPUを占有する待機は避け、必ずOSに制御を返すAPIを使うことが重要です。

また、条件付きコンパイルとラッパー関数を使ってクロスプラットフォーム対応のmsleepを用意すれば、アプリ側からはOS差を意識せずに扱えるようになります。

秒・ミリ秒・Windows対応を押さえて、実用的で移植性の高い時間待機処理を設計してください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!