0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

四日で覚えるC言語【Day 8:ログ出力+メモリ・型・符号理解】

Posted at

はじめに

【C言語】 は、1970年代に開発された汎用プログラミング言語で、現在の多くの言語(C++、Java、C#、Go など)の基礎となっています。

特徴としては、OS、デバイスドライバ、組み込みシステムなど、「ハードウェアに近い低レベルな処理」が可能な点です。

【注意】このメモは、C言語を全くしらない人間が、ChatGPTに聞いて適当に書いている、C言語の基本的な記載方法のまとめです。

(内容は確認しながら書いているつもりですが、間違ってたらごめんなさい)


ファイル出力と構造体の活用,構造体コピーとポインタの動作理解,型のサイズ・符号と2の補数

・共通ログモジュールの作成・利用(世代ローテーション・日時付きログ)

・メモリ管理の理解(構造体のコピーとポインタ、ファイルポインタの扱い)

・nt32_t / int64_t の意味、2の補数の確認(int, long のサイズと符号、2の補数表現での負数計算)


✅ 共通ログモジュール概要

・ファイル名をもとにログ出力
・ファイルサイズが上限を超えたら世代ローテーション(例: sample.log.1~5)
・日時付きログを出力(例: [2025-10-26 04:44] メッセージ)


✅ 実装方針(仕様整理)

機能 関数 内容
① ファイルオープン Log_Open() ログ構造体を受け取り、最新ログファイルを開く。なければ .1 を新規作成。内部関数で最新ログ検索

② ログ書き込み Log_Write() ファイルに追記。サイズ上限を超えたら次世代にローテーション。日時付きログ出力。ローテーション処理は内部関数

③ ファイルクローズ Log_Close() 開いているファイルを閉じる


🧩 common_log.h

#ifndef COMMON_LOG_H
#define COMMON_LOG_H

#include <stdio.h>
#include <time.h>
#include <stdint.h> // 明確なビット幅の型を使うため

// 設定情報をまとめた構造体
typedef struct {
    char base_name[256];   // ベースファイル名(例: "sample.log")
    int32_t max_generation; // 最大世代数(例: 5)
    int64_t max_size;       // 最大ファイルサイズ(バイト単位)
} LogConfig;

// 実際にログを扱うための構造体
typedef struct {
    FILE *fp;              // ファイルポインタ
    LogConfig config;      // 設定情報のコピー
    int current_gen;       // 現在の世代番号(1~max_generation)
} LogHandle;

// 外部から使う関数
int  Log_Open(LogHandle *handle, const LogConfig *config);
int  Log_Write(LogHandle *handle, const char *message);
void Log_Close(LogHandle *handle);

#endif // COMMON_LOG_H


💬 ポイント

①typedef struct {...} LogConfig; で「ログの設定情報」をひとまとめにしています。

②FILE *fp は「開いているファイル」を表すポインタ。C言語ではファイル操作も「メモリ上の構造体」として扱われます。

③int32_t や int64_t は、「何ビットの整数か」を明示できる型。

・int32_t は「32ビット=約21億まで」

・int64_t は「64ビット=超大きな数まで」


🧩 common_log.c

#include "common_log.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

/* 内部専用の関数(モジュール内でのみ使用) */
static int  find_latest_generation(const LogConfig *config);
static int  rotate_log(LogHandle *handle);
static int64_t get_file_size(const char *filename);
static void get_timestamp(char *buffer, size_t size);

// ログを開く
int Log_Open(LogHandle *handle, const LogConfig *config)
{
    if (!handle || !config) return -1; // NULLチェック

    handle->fp = NULL;
    handle->config = *config;  // 構造体の中身をコピー

    int latest_gen = find_latest_generation(config);
    handle->current_gen = (latest_gen == 0) ? 1 : latest_gen;

    char filename[300];
    snprintf(filename, sizeof(filename), "%s.%d", config->base_name, handle->current_gen);

    handle->fp = fopen(filename, "a"); // 追記モードで開く
    if (!handle->fp) { perror("Log_Open"); return -1; }

    return 0;
}

// ログ書き込み
int Log_Write(LogHandle *handle, const char *message)
{
    if (!handle || !handle->fp) return -1;

    char filename[300];
    snprintf(filename, sizeof(filename), "%s.%d", handle->config.base_name, handle->current_gen);

    // サイズ超過時はローテーション
    if (get_file_size(filename) >= handle->config.max_size) {
        if (rotate_log(handle) != 0) return -1;
    }

    char timestamp[32];
    get_timestamp(timestamp, sizeof(timestamp));

    fprintf(handle->fp, "[%s] %s\n", timestamp, message);
    fflush(handle->fp); // バッファを即書き出し

    return 0;
}

// ファイルを閉じる
void Log_Close(LogHandle *handle)
{
    if (handle && handle->fp) {
        fclose(handle->fp);
        handle->fp = NULL;
    }
}

// 最新世代のログを探す
static int find_latest_generation(const LogConfig *config)
{
    time_t latest_time = 0;
    int latest_gen = 0;

    for (int i = 1; i <= config->max_generation; i++) {
        char filename[300];
        snprintf(filename, sizeof(filename), "%s.%d", config->base_name, i);

        struct stat st;
        if (stat(filename, &st) == 0 && st.st_mtime > latest_time) {
            latest_time = st.st_mtime;
            latest_gen = i;
        }
    }
    return latest_gen;
}

// サイズ超過時に次のファイルへ
static int rotate_log(LogHandle *handle)
{
    if (!handle) return -1;
    fclose(handle->fp);
    handle->fp = NULL;

    handle->current_gen++;
    if (handle->current_gen > handle->config.max_generation)
        handle->current_gen = 1;

    char filename[300];
    snprintf(filename, sizeof(filename), "%s.%d", handle->config.base_name, handle->current_gen);

    handle->fp = fopen(filename, "w");
    if (!handle->fp) { perror("rotate_log"); return -1; }

    return 0;
}

// ファイルサイズ取得
static int64_t get_file_size(const char *filename)
{
    struct stat st;
    return (stat(filename, &st) == 0) ? (int64_t)st.st_size : 0;
}

// 現在時刻を "YYYY-MM-DD HH:MM" 形式に
static void get_timestamp(char *buffer, size_t size)
{
    time_t t = time(NULL);
    strftime(buffer, size, "%Y-%m-%d %H:%M", localtime(&t));
}


💬 ポイント

①static 関数は「このファイル内だけで使える」関数。モジュール内の隠し処理です。

②fopen, fclose, fprintf, fflush は「C標準ライブラリのファイル入出力関数」。

③struct stat はファイルの情報(サイズ・更新時刻など)を取得する構造体。

④rotate_log() は「ファイルが大きくなったら次のファイルへ切り替える」処理。

⑤get_timestamp() は現在の日時を文字列に変換しています。


🧪 使用例(main.c)

#include "common_log.h"
#include <stdio.h>
#include <limits.h> // INT32_MAX, INT32_MIN 用

int main(void)
{
    // 設定を準備
    LogConfig cfg = {"sample.log", 5, 1024*2}; // 2KBでローテーション
    LogHandle handle;

    Log_Open(&handle, &cfg);

    // ログを書き込む(100行)
    for (int i = 0; i < 100; i++) {
        char msg[128];
        snprintf(msg, sizeof(msg), "Log message %03d", i);
        Log_Write(&handle, msg);
    }

    Log_Close(&handle);

    // 型と符号の確認
    printf("sizeof(int) = %zu\n", sizeof(int));
    printf("sizeof(long) = %zu\n", sizeof(long));
    printf("int max = %d, int min = %d\n", INT32_MAX, INT32_MIN);

    int8_t x = -5;
    printf("x = %d, 2の補数 = 0x%02X\n", x, (uint8_t)x);

    return 0;
}


💬 ポイント

①LogConfig cfg で設定をまとめて作成します。

②fopen, fclose, fprintf, fflush は「C標準ライブラリのファイル入出力関数」。

③sizeof() で型の大きさをバイト単位で取得します。

④INT32_MAX, INT32_MIN は に定義された定数です。

⑤int8_t x = -5 は、内部的に「2の補数」で 0xFB として保存されます。


💠 実行結果(例:Linux 64bit)

gcc main.c common_log.c -o logtest
./logtest

出力:


sizeof(int) = 4
sizeof(long) = 8
int max = 2147483647, int min = -2147483648
x = -5, 2の補数 = 0xFB



📂 sample.log.1(抜粋)
[2025-10-25 22:01] Log message 000
[2025-10-25 22:01] Log message 001
[2025-10-25 22:01] Log message 002
...

容量が2KBを超えると、自動的に次のファイルへローテーションします。

📂 sample.log.2(抜粋)

[2025-10-25 22:02] Log message 050
[2025-10-25 22:02] Log message 051
...

🧠 メモリと構造体の関係図
LogHandle
├─ FILE *fp → sample.log.1 を指す
├─ LogConfig config → 設定のコピー
│ ├─ base_name = "sample.log"
│ ├─ max_generation = 5
│ └─ max_size = 2048
└─ current_gen = 1

⚙️ メモリコピーのイメージ
cfg(設定元)
┌──────────────────────────┐
│ base_name="sample.log" │
│ max_generation=5 │
│ max_size=2048 │
└──────────────────────────┘
↓ コピー
handle.config
┌──────────────────────────┐
│ base_name="sample.log" │
│ max_generation=5 │
│ max_size=2048 │
└──────────────────────────┘

💡 2の補数(負の数の内部表現)

例:int8_t x = -5

操作 ビット列 値
+5 の2進数 00000101 5
ビット反転 11111010
+1 する 11111011 = 0xFB

→ メモリ上では 11111011 で保存される。
→ これが「2の補数表現」。


💡 ポイントまとめ

①ログモジュール

・Log_Open() : 最新ファイルを自動検出、無ければ作成

・Log_Write() : 追記+サイズ超過でローテーション

・Log_Close() : 安全にファイル閉じる

②メモリ管理

・FILE* はヒープ上の管理

・構造体コピーでメモリ理解

③型とサイズ

・int32_t, int64_t で符号付きのサイズを確認

・sizeof() で実際のサイズを確認

④符号と2の補数

・負数は2の補数で内部表現される

・8bit例で -5 = 0xFB









0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?