基本を終えた人向け。const・enum・typedef・errno・可変長引数・条件コンパイル。
const は「書き換えない約束」を型で表現する機能。関数の意図を明確にし、誤操作をコンパイル時に検出できる。// 引数の配列を読むだけで書き換えないことを明示 int sum_array(const int *a, int n) { int s = 0; for (int i = 0; i < n; i++) s += a[i]; // a[i] = 0; ← コンパイルエラー(read-only) return s; }
const int *p → *p は書き換え不可、p は変更可int * const p → p は変更不可、*p は書き換え可const int * const p → どちらも不可* の位置を基準に、右側が変更可否・左側が中身の可否。
const int MAX = 100; // マクロ #define より型安全 const double PI = 3.14159265358979;
const を付ける。これは呼び出し側への契約になる。switch との相性がよく、コードの可読性が大幅に上がる。// マジックナンバーを使った良くない例 int state = 2; if (state == 3) { /* 3 って何? */ } // enum で意図を明確に enum Color { RED, GREEN, BLUE }; // 0, 1, 2 enum Color c = GREEN; if (c == RED) { /* 意味が伝わる */ }
enum HttpStatus { HTTP_OK = 200, HTTP_NOT_FOUND = 404, HTTP_SERVER_ERROR = 500 };
enum State { STATE_IDLE, STATE_RUN, STATE_STOP }; void handle(enum State s) { switch (s) { case STATE_IDLE: printf("idle\n"); break; case STATE_RUN: printf("run\n"); break; case STATE_STOP: printf("stop\n"); break; } }
-Wswitch-enum を付けると、enum のある値が switch で扱われていないときに警告してくれる。// 構造体の typedef typedef struct { int x; int y; } Point; Point p = {3, 4}; // 'struct Point' と書かなくて良い // 関数ポインタ typedef int (*CmpFn)(const void *, const void *); CmpFn cmp; // 関数ポインタをシンプルに宣言 // 配列も可能 typedef char Buffer[256]; Buffer name; // char name[256] と同等
| typedef 型 | 実体例 | 用途 |
|---|---|---|
size_t | unsigned long | サイズ・インデックス |
ssize_t | long | 符号付きサイズ |
int32_t / uint64_t | int / unsigned long | ビット幅を明示 |
time_t | long | UNIX時刻 |
FILE | struct ... | ファイルハンドル |
typedef struct S *SPtr;)。この慣習は可読性を下げるので、ポインタ型は素直に書く派が多い。errno にエラーコードをセットする。perror や strerror で人間可読なメッセージに変換できる。#include <stdio.h> #include <string.h> #include <errno.h> int main(void) { FILE *fp = fopen("no_such_file.txt", "r"); if (fp == NULL) { // 方法1: perror(メッセージを標準エラーに) perror("fopen"); // → fopen: No such file or directory // 方法2: strerror(エラーコード) fprintf(stderr, "error %d: %s\n", errno, strerror(errno)); return 1; } fclose(fp); return 0; }
| 定数 | 意味 |
|---|---|
ENOENT | ファイルが存在しない |
EACCES | アクセス権がない |
ENOMEM | メモリ不足 |
EINVAL | 引数が無効 |
EAGAIN | リソース一時不足(再試行可) |
EINTR | シグナルで中断された |
errno は関数が失敗を返したときだけ見る。成功時の値は未定義。errno = 0; とリセットする習慣を付けると誤解が減る(特に strtol など)。<stdarg.h> を使う。#include <stdio.h> #include <stdarg.h> // int を可変個受け取って合計を返す // 第1引数は「いくつあるか」を渡す int sum(int count, ...) { va_list ap; va_start(ap, count); // 可変長引数の読み取り開始 int total = 0; for (int i = 0; i < count; i++) { total += va_arg(ap, int); // 1個取り出す } va_end(ap); // 後片付け return total; } int main(void) { printf("%d\n", sum(3, 10, 20, 30)); // 60 printf("%d\n", sum(5, 1, 2, 3, 4, 5)); // 15 }
va_arg の第2引数には実際の型を書く(va_arg(ap, int))。型を間違えるとUB。va_arg(ap, char) ではなく va_arg(ap, int)。// printfに渡すラッパー。vfprintfに可変長引数をそのまま渡す void log_info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[INFO] "); vfprintf(stderr, fmt, ap); // va_list版のfprintf fprintf(stderr, "\n"); va_end(ap); } // 使い方: log_info("user %s logged in at %d", name, ts);
#ifdef DEBUG printf("x = %d\n", x); #endif // gcc -DDEBUG を付けてコンパイルすると上のprintfが有効 // デフォルトでは消える
#define VERSION 3 #if VERSION >= 2 // v2以降の機能 #else // v1の機能 #endif
#if defined(_WIN32) #include <windows.h> Sleep(1000); #elif defined(__APPLE__) || defined(__linux__) #include <unistd.h> sleep(1); #else #error "unsupported platform" #endif
| マクロ | 意味 |
|---|---|
__FILE__ | 現在のファイル名 |
__LINE__ | 現在の行番号 |
__func__ | 関数名(C99以降) |
__DATE__ / __TIME__ | コンパイル日時 |
__STDC_VERSION__ | C規格バージョン |
// デバッグ用の便利マクロ #define LOG(...) \ fprintf(stderr, "[%s:%d %s] ", __FILE__, __LINE__, __func__), \ fprintf(stderr, __VA_ARGS__), \ fprintf(stderr, "\n") LOG("x = %d", x); // 出力: [main.c:42 main] x = 10
#ifndef XXX_H / #define / #endif は標準だが、#pragma once はコンパイラ拡張(ただし広くサポートされる)。この講座の理解度をチェックしましょう!
*のどちら側にconstがあるかで、const修飾の対象が「指す先」か「ポインタ自体」かが変わります。
enumは明示しなければ0から始まり、順に1ずつ増えます。途中で値を指定して続きを変えることも可能です。
va_list / va_start / va_arg / va_end が