はじめに
【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