C言語基礎|前回実行時の情報を取得

「前回いつ実行したかが分かるだけで、プログラムは“道具”になる。」

プログラムは終了するとメモリの中身が消えてしまいます。だからこそ、前回実行したときの情報を覚えておけると、とても便利になります。

たとえば「前回いつ実行したか」が分かれば、ログの起点になったり、更新タイミングの判断に使えたりします。
この節では、前回実行時の日付と時刻をファイルに保存しておき、次回起動時にそれを読み取って表示する方法を、やさしく丁寧に身につけていきます。

まず全体像:起動時に読む、終了時に書く

図:前回時刻の引き継ぎフロー

起動
  ↓
datetime.dat を読み取りで開く(r)
  ↓
読めたら「前回時刻」を表示 / 開けなければ「初回」を表示
  ↓
現在時刻を取得(time → localtime)
  ↓
datetime.dat を書き込みで開く(w)
  ↓
今回時刻を書き込む(fprintf)
  ↓
終了

図の説明

  • r で開けないなら、保存ファイルが無い=初回実行の可能性が高い、という判断ができます。
  • 終了時に w で保存しておくと、次回起動時にそれが「前回情報」になります。
  • “読む→表示→書く”の順番が基本の型です。

サンプルプログラム(前回の日付と時刻を表示)

「前回の日付と時刻」を表示するプログラム例です。
保存形式は、読み書きしやすいように「年 月 日 時 分 秒」をスペース区切りで1行にします。

プロジェクト名:chap13-7-1 ソースファイル名:chap13-7-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスに「datetime.dat」が作成されます。
C:\Users\<ユーザー名>\source\repos\chap13-7-1\chap13-7-1

#include <time.h>
#include <stdio.h>

char data_file[] = "datetime.dat";   /* 保存ファイル名 */

/* 前回の日付・時刻を取得して表示する */
void show_previous_datetime(void)
{
    FILE *fp = fopen(data_file, "r");
    if (fp == NULL) {
        /* 保存ファイルが無い=初回実行の可能性が高い */
        printf("This looks like the first run.\n");
        return;
    }

    int year, month, day, hour, min, sec;

    /* 6項目を読み取れたかで判定する */
    if (fscanf(fp, "%d%d%d%d%d%d", &year, &month, &day, &hour, &min, &sec) == 6) {
        printf("Last run was at %04d-%02d-%02d %02d:%02d:%02d.\n",
               year, month, day, hour, min, sec);
    } else {
        printf("Previous data is missing or broken.\n");
    }

    fclose(fp);  /* クローズ */
}

/* 今回の日付・時刻を書き込む(次回の前回情報になる) */
void save_current_datetime(void)
{
    FILE *fp = fopen(data_file, "w");
    if (fp == NULL) {
        printf("ERROR: Cannot open datetime.dat for writing.\n");
        return;
    }

    /* 現在の暦時刻を取得して、地方時の要素別時刻に変換する */
    time_t current = time(NULL);
    struct tm *t = localtime(¤t);

    if (t == NULL) {
        printf("ERROR: Failed to get local time.\n");
        fclose(fp);
        return;
    }

    /* 年 月 日 時 分 秒 を保存する(スペース区切り) */
    fprintf(fp, "%d %d %d %d %d %d\n",
            t->tm_year + 1900,   /* 年は1900年からの差なので補正 */
            t->tm_mon + 1,       /* 月は0始まりなので補正 */
            t->tm_mday,          /* 日 */
            t->tm_hour,          /* 時 */
            t->tm_min,           /* 分 */
            t->tm_sec            /* 秒 */
    );

    fclose(fp);  /* クローズ */
}

int main(void)
{
    show_previous_datetime();   /* 前回情報を表示 */
    save_current_datetime();    /* 今回情報を保存 */
    return 0;
}

保存ファイル datetime.dat の中身(フォーマット)

このプログラムは、datetime.dat に次の形式で1行保存します。

保存形式(1行)

項目説明
年 月 日 時 分 秒2026 2 15 22 10 3スペース区切りの6個の整数

表の説明

  • 読み取り側は fscanf で整数6個を取るだけなので、とても単純です。
  • 表示用にゼロ埋めしたい場合は printf 側で %04d や %02d を使えばOKです。

初回と2回目以降で何が起きる?

実行回数と表示

実行回数show_previous_datetime の表示save_current_datetime の動作
1回目This looks like the first run.datetime.dat を作って現在時刻を保存
2回目以降Last run was at YYYY-MM-DD HH:MM:SS.datetime.dat を上書きして今回時刻を保存

表の説明

  • 初回は datetime.dat が存在しないので r で開けず、初回メッセージになります。
  • 2回目は前回保存した時刻が読めるので、前回時刻が表示されます。
  • 保存は w なので毎回最新の時刻に更新されます。

使う命令(関数)の書式を整理

fopen

項目内容
ヘッダ#include <stdio.h>
形式FILE *fopen(const char *filename, const char *mode);
モード例r(読み取り), w(書き込み・新規/切り捨て)
失敗NULL

fscanf

項目内容
ヘッダ#include <stdio.h>
形式int fscanf(FILE *stream, const char *format, ...);
返り値成功した代入項目数(今回なら 6 が成功)

fprintf

項目内容
ヘッダ#include <stdio.h>
形式int fprintf(FILE *stream, const char *format, ...);
返り値書き込んだ文字数(エラー時は負)

fclose

項目内容
ヘッダ#include <stdio.h>
形式int fclose(FILE *stream);
役割フラッシュして閉じる

time

項目内容
ヘッダ#include <time.h>
形式time_t time(time_t *t);
使い方例current = time(NULL);

localtime

項目内容
ヘッダ#include <time.h>
形式struct tm *localtime(const time_t *timep);
役割暦時刻を地方時の要素別時刻に変換

表の説明

  • 前回情報の読み取りは fopen の成否と fscanf の戻り値で判断します。
  • 現在時刻は time → localtime の流れで取得します。
  • tm_year と tm_mon の補正(+1900、+1)は必須です。

図で理解:なぜ tm_year と tm_mon は補正が必要?

図:struct tm の “クセ”

tm_year = 1900年からの年数  → 表示は +1900
tm_mon  = 0が1月、11が12月 → 表示は +1

図の説明

  • struct tm は「人間が読むための構造体」ですが、年と月だけは内部表現が独特です。
  • ここを補正して表示・保存するのが定番です。

演習問題

演習13-5:前回の時刻+メモを保存する

前回実行時刻を表示した後、今回のメモ(短い文字列)を入力し、時刻と一緒に保存せよ。
次回起動時に「前回は…で、メモは…でした」と表示すること。

解答方針(保存形式)

  • 1行目:年 月 日 時 分 秒
  • 2行目:メモ文字列(fgetsで読む)

解答例

プロジェクト名:chap13-7-2 ソースファイル名:chap13-7-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスに「datetime_memo.dat」が作成されます。
C:\Users\<ユーザー名>\source\repos\chap13-7-2\chap13-7-2

#include <time.h>
#include <stdio.h>

char data_file[] = "datetime_memo.dat";

void show_previous(void)
{
    FILE *fp = fopen(data_file, "r");
    if (fp == NULL) {
        printf("No previous record found.\n");
        return;
    }

    int y, mo, d, h, mi, s;
    char memo[200];

    if (fscanf(fp, "%d%d%d%d%d%d\n", &y, &mo, &d, &h, &mi, &s) == 6 &&
        fgets(memo, sizeof memo, fp) != NULL) {
        printf("Last run: %04d-%02d-%02d %02d:%02d:%02d, memo: %s",
               y, mo, d, h, mi, s, memo);
    } else {
        printf("Previous record is broken.\n");
    }

    fclose(fp);
}

void save_current(void)
{
    /* メモ入力 */
    char memo[200];
    printf("Enter a memo for this run: ");

    /* 入力状態によって改行が残る場合があるので掃除 */
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) { }

    if (fgets(memo, sizeof memo, stdin) == NULL) {
        printf("ERROR: Input failed.\n");
        return;
    }

    FILE *fp = fopen(data_file, "w");
    if (fp == NULL) {
        printf("ERROR: Cannot open save file.\n");
        return;
    }

    time_t current = time(NULL);
    struct tm *t = localtime(¤t);
    if (t == NULL) {
        printf("ERROR: Failed to get local time.\n");
        fclose(fp);
        return;
    }

    fprintf(fp, "%d %d %d %d %d %d\n",
            t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
            t->tm_hour, t->tm_min, t->tm_sec);
    fprintf(fp, "%s", memo);

    fclose(fp);
    printf("Saved.\n");
}

int main(void)
{
    show_previous();
    save_current();
    return 0;
}