本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

C チートシート — クイックリファレンス

C(C11/C17)の構文、ポインタ、メモリ、文字列、構造体、ファイル I/O を網羅する自己完結型の簡潔なリファレンス。さらにスレッド、ソケット、正規表現、時間、プロセス、ビルド/デバッグの要点も収録。日常の約80%のニーズをカバーします。

C

C C17 (ISO/IEC 9899:2018)

ISO C · 手続き型、手動メモリ管理 · 静的・弱い型付け(キャストあり)

おすすめの学習パス

C の初心者ですか?次の順序で読み進めてください。 1. Hello World — コンパイル、実行、終了コード 2. 変数と定数 — 型、サイズ、const 3. ポインタと配列 — &、*、->、ポインタ演算 4. 制御フロー — if / switch / ループ 5. 関数と可変長引数 — 値渡し vs ポインタ渡し 6. 文字列と変換 — printf フォーマット表 必要になったらファイル I/O とエラー処理を参照してください。構造体、オブジェクト風パターン、上級セクションは慣れてから見てください。 特定のタスクがありますか?上の検索ボックスを使ってください — 「malloc」「socket」「pthread」「qsort」「regex」を試してください。

1.Hello World とビルド環境

C プログラムのコンパイル・実行・構成:ツールチェーン、IDE の選択、複数ファイル、コマンドライン引数、終了コード。

最小プログラム

すべての C プログラムは main() から実行を開始し、int 型のステータスをオペレーティングシステムへ返す:0 なら成功、それ以外は失敗を示す。先頭の #include が宣言を取り込み、コンパイラは printf が何であるかを知ることができる。

1
2
3
4
5
6
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
return 0; // 0 = success to the OS
}

コンパイルと実行(gcc / clang)

C はインタープリタ型ではなくコンパイル型言語である:コンパイラはソースコードを実行ファイルへ翻訳し、それを実行する。常に警告(-Wall -Wextra)を有効化し、言語標準(-std=c17)を固定することで、可搬性の問題をコンパイル時に表面化させる。

1
2
3
4
5
6
7
8
9
// $ gcc hello.c -o hello
// $ ./hello
//
// With warnings + standard version:
// $ gcc -std=c17 -Wall -Wextra -pedantic hello.c -o hello
// $ clang -std=c17 -Wall -Wextra hello.c -o hello // macOS
//
// Windows (MinGW):
// $ gcc hello.c -o hello.exe && hello.exe

ツールチェーンと IDE のリスト

コンパイラとエディタが必要である。一般的な組み合わせ:Linux では gcc、macOS では clang、Windows では MinGW-w64 または MSVC、いずれも VS Code や CLion から呼び出せる。まずバージョン確認コマンドを一度実行してツールチェーンが利用可能であることを確かめてから、別の問題を調べること。

1
2
3
4
// gcc (Linux) / clang (macOS) / MinGW-w64 (Windows)
// VS Code + C/C++ extension (tasks.json compiles)
// CLion / Visual Studio / Xcode
// Verify: $ gcc --version

コマンドライン引数

main はユーザーがコマンドラインで入力した内容を受け取れる:argc は引数の個数、argv[] は引数そのもの——argv[0] は常にプログラム名である。引数は文字列として解析し、数値が必要なら strtol/strtod で変換する。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(int argc, char **argv) {
printf("%d args\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
// $ ./prog a b c -> argv[0]=./prog argv[1]=a ...

複数ファイル

大規模なプログラムは複数の .c ファイルに分割し、共有の .h ヘッダファイルに宣言を置く。各 .c ファイルは個別にコンパイルされ、その後リンクされる;ヘッダファイルが宣言と定義の一致を保ち、署名を変更すれば、それを使うすべての箇所が同時に露出する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// utils.h — declarations (prototypes)
#ifndef UTILS_H
#define UTILS_H
int add(int a, int b);
#endif
//
// utils.c — definitions
#include "utils.h"
int add(int a, int b) { return a + b; }
//
// main.c
#include <stdio.h>
#include "utils.h"
int main(void) { printf("%d\n", add(2, 3)); return 0; }
//
// $ gcc main.c utils.c -o app

終了コード

main が返す値はシェルへ伝わる:0(EXIT_SUCCESS)は成功、それ以外は失敗を示す。スクリプトや CI はこの規約に依存しているため、終了コードには意味を持たせる必要がある。Linux/macOS では $? で、Windows では %ERRORLEVEL% で確認できる。

1
2
3
4
5
6
#include <stdlib.h>
int main(void) {
if (error) return EXIT_FAILURE; // 1
return EXIT_SUCCESS; // 0
}
// Shell: $ echo $? (Linux/macOS) or %ERRORLEVEL% (Windows)

環境変数

getenv() はプロセス環境から変数を読み出し、その値を返す、未設定なら NULL を返す。再コンパイルせずに設定を注入する標準的な手段である——ただし、読み出した値は信頼できない入力として扱うこと。

1
2
3
4
#include <stdlib.h>
#include <stdio.h>
const char *path = getenv("PATH");
if (path) printf("%s\n", path); // may be NULL if unset

プリプロセッサの基礎

プリプロセッサはコンパイル前に動作する:#define はマクロを定義し、# と ## はトークンを連結し、#ifdef/#if は条件付きコンパイルを行う。これは純粋なテキスト処理で、型を持たない層である——多くの場面では本物の関数や const を優先すべきである。

1
2
3
4
5
6
7
8
9
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define STR(x) #x // "x" as string
#define CONCAT(a, b) a##b // tokens joined
#ifdef DEBUG
printf("debug build\n");
#endif
#if defined(__cplusplus)
// compiled as C++
#endif

2.変数と定数

宣言、型のサイズ、定幅整数、const の位置、記憶クラス、typedef、列挙型。

基本的な宣言

変数を宣言するには型を先に書き、名前をその後ろに書き、宣言位置で初期化する(C99+)。宣言時に初期化することで、後から初期化されていないゴミの値を読み出すのを避ける。

1
2
3
4
int counter = 0;
double pi = 3.14159;
char letter = 'A';
_Bool flag = 1; // or <stdbool.h> -> bool

型のサイズ(64 ビットでの典型値)

C は最小サイズのみを保証し、int はプラットフォームによって 16 ビットにも 32 ビットにもなり得る。sizeof を使って対象プラットフォームの実サイズを出力し、正直に扱い、バイト数をハードコードしてはならない。

1
2
3
4
5
6
7
8
9
// char 1 byte (-128..127)
// short 2 bytes (-32,768..32,767)
// int 4 bytes
// long 8 bytes (4 on Windows)
// long long 8 bytes (always >= 64 bit)
// float 4 bytes (6-7 sig digits)
// double 8 bytes (15-16 sig digits)
printf("int=%zu long=%zu ptr=%zu\n",
sizeof(int), sizeof(long), sizeof(void*));

固定幅整数型(C99+、<stdint.h>)

メモリ配置を厳密に制御したい場合——ファイルフォーマット、ネットワークプロトコル、ハッシュ——には <stdint.h> の int32_t/uint64_t などを使う。これらはプラットフォームが実際に提供する場合にのみ存在し、正確な幅を持つ名前が好まれる理由はまさにそこにある。

1
2
3
4
5
6
#include <stdint.h>
int32_t exact = 42; // exactly 32 bits
uint64_t big = 1ULL << 40;
int_least16_t small; // >= 16 bits
intptr_t addr; // big enough for a pointer
printf("%d\n", (int)sizeof(int32_t)); // always 4

符号なし / 符号あり

符号なし型は決して負にならず、2^N の剰余で桁あふれする;符号あり型は負になり得る。両者を同一の式で混在させると、符号ありのオペランドは符号なしへ昇格される——これは典型的な落とし穴である(faq 節を参照)。

1
2
3
unsigned int u = 4000000000U;
signed long long s = -9LL;
// Mixing signed + unsigned promotes to unsigned — beware (see faq)

const——読み取り専用(コンパイル時検査)

const は変数を読み取り専用とし、コンパイラはビルド時に意図しない書き込みを拒否する。変更してはならないものにはすべて const を付ける:意図を示すだけでなく、コンパイラの最適化にも役立つ。

1
2
const int MAX = 100;
// MAX = 5; // ERROR

const ポインタの位置(定番の混同ポイント)

ポインタ宣言における const の位置は、何を保護するかを決める:* の前は指し先の値を、* の後はポインタ自体を保護する。型を右から左へ読めば、この 2 行の違いが見えてくる。

1
2
3
const int *p1; // pointer to const int — *p1 read-only, p1 reassignable
int *const p2; // const pointer to int — p2 read-only, *p2 writable
const int *const p3; // both read-only

記憶域クラス

記憶域クラスは変数がどこに存在し、どれだけの間生存するかを決める。auto は局所変数の既定である;ファイルスコープに static を付けると名前をファイル外から見えなくする;局所変数に static を付けると呼び出しをまたいで値を保つ;extern は別所で定義された名前を参照する。

1
2
3
4
5
static int file_scope = 0; // internal linkage (file-local)
extern int from_other; // defined in another file
static void helper(void) {} // file-local function
// 'static' on a local: persists across calls
void count(void) { static int n = 0; n++; printf("%d\n", n); }

typedef——型の別名

typedef は型に新しい名前を付ける。新しい型を作るのではなく単なる別名である——しかし別名があれば、より短く一貫した型を書け、struct や関数ポインタの冗長さを読みやすい一語にまとめられる。

1
2
3
typedef unsigned long size_type;
typedef struct { int x, y; } Point; // anonymous struct + alias
Point p = { 1, 2 };

enum——名前付き定数

enum は関連する整数定数の集まりに名前を付ける。値は 0 から始まり 1 ずつ増えるが、明示的に代入したり範囲を指定したりすることもできる。列挙はそれ自体がドキュメントの役割を果たし、switch にも利用できる。

1
2
3
enum Color { RED, GREEN = 5, BLUE }; // 0, 5, 6
typedef enum { OK, WARN, ERR } Status;
Status s = OK;

指定初期化(C99+)

C99 ではメンバを名前で初期化する(.port = 8080)ことや、配列要素を添字で初期化すること([3] = 9)ができ、順序は任意である。指定されなかったメンバは自動的にゼロで初期化される——長い位置指定の初期化よりずっと明快である。

1
2
3
4
5
int arr[5] = { [0] = 1, [3] = 9 }; // 1 0 0 9 0
struct Config cfg = {
.host = "localhost",
.port = 8080,
};

複合リテラル(C99+)

複合リテラルはキャストに似た構文で、一時的な struct/配列値をインラインで構築する。一度きりの値を関数に渡したいとき、名前付きの変数を先に宣言する必要がなく便利である。

1
2
void draw(struct Point pt);
draw((struct Point){ .x = 3, .y = 4 });

volatile(メモリマップド I/O / シグナル)

volatile はコンパイラに対し、ある変数がプログラムの通常の流れ以外で変化し得ることを伝える——ハードウェアレジスタ、シグナルハンドラ、別のスレッドなど。これにより、コンパイラによるアクセス時のキャッシュや並べ替えが抑止される。

1
volatile uint32_t *reg = (uint32_t *)0xFFFF0000;

3.構造体・共用体・集約型

構造体のレイアウト、共用体、ビットフィールド、sizeof、_Static_assert、アライメント —— 実戦的な C データの構成要素。

struct——異種レコード

struct は異なる型ではあるが互いに関連する値を一つのレコードにまとめる。メンバは宣言順に格納され、間にはアラインメント用のパディングが挿入される;したがって sizeof(struct) はメンバのサイズの合計より大きくなることが多い。

1
2
3
4
struct Point { int x; int y; };
struct Point p = { 1, 2 }; // positional
struct Point q = { .y = 5 }; // designated (rest zero)
p.x = 10;

struct へのポインタ——-> でメンバにアクセス

アロー演算子 p->member は (*p).member の略記であり、構造体ポインタを逆参照してメンバへアクセスする。構造体をポインタで渡すとサイズが節約でき(一語分)、関数が呼び出し側のレコードを書き換えることも可能になる。

1
2
struct Point *pp = &p;
pp->x = 20; // same as (*pp).x

無名 struct + typedef

タグのない struct に typedef を付けることができ、一つの宣言でクリーンな型名を得ることができる。これはよく使うレコード型に名前を付ける慣習的な方法であり、处处に struct と書く手間を省く。

1
2
3
struct { int a, b; } ab; // unnamed type, single var
typedef struct { int a, b; } Pair;
Pair pr = { 3, 4 };

union——重なる記憶領域(サイズ = 最大のメンバ)

union は複数のメンバを同一のメモリ領域に置き、サイズは最大のメンバに従う。最後に書き込んだメンバのみが有効である——他のメンバを読むのは未定義動作となるため、タグ用のフィールドを併設する必要がある。

1
2
3
4
5
6
7
union Value {
int i;
float f;
char bytes[4];
};
union Value v = { .f = 3.14f };
// v.i and v.f share the same memory (implementation-defined reading)

ビットフィールド——フラグの詰め込み

ビットフィールドは複数の小さなフラグを整数のビットに詰め込み、多くのブール値を含むレコードのメモリを節約する。レイアウトはコンパイラが決定するため、ビットフィールドは単一の翻訳単位内に留め、ABI の境界をまたいではならない。

1
2
3
4
5
6
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 3;
};
struct Flags f = { 1, 0, 2 };

sizeof——オブジェクトの占有バイト数

sizeof は型やオブジェクトが占めるバイト数を与える。スコープ内にまだある配列に対しては sizeof a / sizeof a[0] で要素数が得られる——ただし配列がポインタに_decay した時点で、このテクニックは静かに機能しなくなる(faq 参照)。

1
2
3
4
5
size_t n = sizeof(int); // 4 on typical systems
size_t sz = sizeof p; // sizeof(struct Point)
int arr[10];
size_t len = sizeof arr / sizeof arr[0]; // 10 (array length)
// NOTE: sizeof(arr) inside a function that received it decays (see pointers)

_Static_assert(C11+)コンパイル時検査

_Static_assert(cond, msg) はコンパイル時のアサーションであり、cond が偽ならビルドが失敗する。これはサイズや ABI への前提が崩れたその瞬間に検証するのに適しており、ランタイムまで持ち越さない。

1
2
_Static_assert(sizeof(void*) == 8, "64-bit only");
_Static_assert(sizeof(int) >= 4, "int too small");

アラインメント——& _Alignof(C11+)

アラインメントはオブジェクトをメモリ上のどのアドレスに置けるかを決める;コンパイラは struct に隙間をパディングし、各メンバを自然なアラインメントに揃える。_Alignof はオブジェクトのアラインメント値を報告し、_Alignas は必要なときにそれを上げる(SIMD、キャッシュラインの共有)。

1
2
3
4
5
#include <stdalign.h>
_Alignof(int) // alignment requirement, e.g. 4
struct S { char c; int i; }; // padded: sizeof(S) often 8, not 5
// Force packing (non-portable, avoid unless needed):
// #pragma pack(push, 1) ... #pragma pack(pop)

フレキシブル配列メンバ(C99+)——struct 末尾の配列

struct は不定長の配列(data[])で終端でき、長さは割り当て時に決まる:1 回の malloc で struct に加えて追加バイトを確保する。これはバッファサイズが実行時まで分からない C の慣用パターンである。

1
2
3
4
5
6
struct Buffer {
size_t len;
char data[]; // last member only
};
struct Buffer *buf = malloc(sizeof(*buf) + 100);
buf->len = 100;

列挙を独立した型として扱う(C23)

C23 では列挙の基底型を固定できる(enum Status : unsigned char)、サイズはコンパイラが決めるものではない。それ以外の場合、列挙は int と互換であり、ほとんどのコードではそれで十分である。

1
2
3
enum Status { ST_OK = 0, ST_ERR = 1 };
// C23 lets you pin the type:
// enum Status : unsigned char { ST_OK = 0, ST_ERR = 1 };

4.ポインタと配列

アドレス取得、デリファレンス、ポインタ演算、配列の減衰、ポインタ vs 配列、const の正しさ。

アドレス取得(&)と逆参照(*)

& はオブジェクトのアドレスを取り出してポインタを得る;* は逆の操作で、ポインタを介して読み書きする。この 2 つを組み合わせることで、値を間接的に参照し、別のスコープからそれを書き換えられる。

1
2
3
4
int x = 42;
int *p = &x; // p holds the address of x
printf("%d\n", *p); // 42 (*p reads x)
*p = 7; // writes through the pointer -> x == 7

NULL——空ポインタ

NULL は「何も指していない」ポインタであり、合法なアドレスとは区別される。これを逆参照するのは未定義動作であり、通常は即座にクラッシュする——したがって使う前に必ず NULL かどうかを判定すること。

1
2
3
4
int *p = NULL;
if (p) { /* non-null */ }
if (!p) { /* null */ }
// Dereferencing NULL is undefined behavior — always check.

ポインタ演算(sizeof(*p) でスケール)

ポインタに整数を加えるのは要素単位での前進であり、バイト単位ではない——p + 2 は 2 つの int を飛び越す。添字 p[i] は実は *(p + i) のことである。配列の末尾を越えるのは未定義動作である。

1
2
3
4
5
int arr[5] = { 10, 20, 30, 40, 50 };
int *p = arr; // == &arr[0]
*(p + 2) // arr[2] == 30
p[2] // same — [] is sugar for *(p + n)
*(p + 5) // UB — past the end

配列の_decay(式はポインタになる)

ほとんどの式において、配列名は先頭要素を指すポインタとなる。結果として、関数内では引数から配列の長さを復元できない——長さを配列と一緒に渡す必要がある。

1
2
3
4
5
void sum(int *a, int n);
int data[10];
sum(data, 10); // 'data' decays to int* — length must be passed
// sizeof(data) == 40 here (true array);
// inside sum(), sizeof(a) == 8 (pointer) — see faq

ポインタ vs 配列

char の配列は自らがバイトを保有し、書き換え可能である;文字列リテラルを指す char* は通常、読み取り専用メモリを指す。リテラルを指すポインタを通じて書き込むのは未定義動作であり、多くの場合クラッシュする。

1
2
3
4
// char s[] = "hi"; mutable array, 3 bytes (incl '\0')
// char *p = "hi"; pointer to string literal (read-only in practice)
s[0] = 'H'; // OK for array
// p[0] = 'H'; // UB (literal usually in read-only memory)

多段階ポインタ

ポインタへのポインタ(int**)は一段間接性が増える。これは文字列の配列、二次元構造、そして「関数がメモリを確保し、呼び出し側が解放する責任を負う」出力引数に使われる。

1
2
3
4
int x = 1;
int *p = &x;
int **pp = &p; // pointer to pointer
**pp = 2; // writes x

関数ポインタ

関数ポインタは関数のアドレスを保持し、間接的に呼び出すことができる。これはコールバック、Dispatch テーブル、oop 節で扱うオブジェクト指向パターンの基盤である。

1
2
3
4
5
6
7
int add(int a, int b) { return a + b; }
int (*fp)(int, int) = add; // pointer to function
fp(1, 2); // call through it
// Cleaner with typedef:
typedef int (*BinOp)(int, int);
BinOp op = add;
op(2, 3);

ポインタの配列(argv など)

char* の配列は文字列のリストを保持する慣用的な入れ物であり、argv がその代表例である。各要素はそれぞれ NUL 終端された文字列を指し、配列自体はポインタの並びに過ぎない。

1
2
3
const char *names[] = { "alice", "bob" };
// pointer-to-array (rare):
int (*row)[5]; // pointer to int[5]

const 正確性

ポインタ引数を const として宣言する——たとえば const char* s——ことは、関数が指し先の内容を変更しないと約束することに等しい。呼び出し側は文字列リテラルや配列を安心して渡せ、コンパイラはこの約束を強制する。

1
2
3
void f(const int *p); // promise not to modify *p
int arr[5] = {1,2,3,4,5};
f(arr); // int[] decays to int*, fits const int*

配列へのポインタ vs ポインタの配列

int *a[5] は 5 つのポインタからなる配列である;int (*b)[5] は 5 つの int の配列を指すポインタである。括弧がすべてを決める。複雑な宣言は右から左へ読むか、cdecl で復号する。

1
2
3
4
5
// int *a[5]; array of 5 pointers (to int)
// int (*b)[5]; pointer to an array of 5 ints
// int (*fn)(int, int); function pointer
// int *fn(int, int); function returning int*
// Use a parser / cdecl tool: https://cdecl.org

5.制御フロー

if/else、三項演算子、switch、for、while、do-while、break/continue、goto、setjmp/longjmp。

if / else if / else

条件によって分岐する:最初の条件が真の分岐が実行され、そうでなければ else へ進む。else-if の連鎖は順に複数の候補を試し、最初に一致した時点で止まる。

1
2
3
4
5
6
7
if (n > 0) {
puts("positive");
} else if (n == 0) {
puts("zero");
} else {
puts("negative");
}

三項演算子(式)

cond ? a : b は文ではなく式である:cond に応じて a か b を取る。簡潔な値の選択に適している;1 行に収まらない、あるいは読みにくいなら if/else に切り替える。

1
2
const char *sign = n > 0 ? "pos" : "non-pos";
int max = (a > b) ? a : b;

switch——break しないとフォールスルー

switch は整数値で振り分ける。break しない限り、実行は次の case へフォールスルーする——意図的にフォールスルーを利用するか(空 case の積み重ね)、各 case で break する。default は一致しない値を処理する。

1
2
3
4
5
6
7
8
9
10
11
switch (status) {
case 200:
case 201:
puts("ok");
break;
case 404:
puts("missing");
break;
default:
puts("other");
}

for——init でカウンタを宣言(C99+)

for(init; cond; step) は標準的なカウントループである。init 部でカウンタを宣言する(C99+)ことでその変数をループ内に閉じ込め、外側の同名の変数との衝突を防げる。

1
2
3
4
5
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
// Empty init / decrement: while-style
for (;;) { if (stop) break; }

while / do-while

while は各反復の前に条件を判定し、1 回も実行されないことがある。do-while は常に 1 回実行してから判定する——メニューや確認プロンプトのような「少なくとも 1 回は尋ねる」場面に適している。

1
2
3
4
while (n < 10) { n++; }
do {
process(input);
} while (!done); // body runs at least once

break / continue

break は最も内側のループまたは switch を即座に抜ける;continue は次の反復へ飛ぶ。うまく使えばネストを平坦化し、フラグ変数を排除できる;使い方を誤ると制御フローを隠してしまうため、層を少なく保つように努める。

1
2
3
4
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // skip 3
if (i == 7) break; // stop at 7
}

goto——深くネストした箇所から抜ける(控えめに、慎重に)

goto はある行のラベルへジャンプする。正当な用途は深くネストした箇所からの脱出と、エラー処理の一元化——すなわち goto-cleanup イディオムである。それ以外では、return やフラグ変数を用いる方がほぼ常に明快である。

1
2
3
4
5
6
7
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (found(i, j)) goto done;
}
}
done:
printf("found at %d,%d\n", i, j);

setjmp / longjmp 非局所ジャンプ

setjmp は実行コンテキストを保存し、longjmp が任意の位置からそれを復元し、保存点まで一気に巻き戻る。その間あらゆるクリーンアップは飛ばされ——デストラクタは実行されない——goto や明示的な return に比べて、通常は見合わない。

1
2
3
4
5
6
7
8
#include <setjmp.h>
jmp_buf env;
if (setjmp(env) == 0) {
risky(); // may longjmp back here
} else {
puts("recovered");
}
// longjmp(env, 1) from anywhere restores the saved context

ループのイディオム

覚えておく価値のあるループの形:カウント範囲に対するインデックスループ、配列へのポインタ走査、break を使った無限ループ。意図を最も直接的に表現できるものを選ぼう。

1
2
3
for (size_t i = 0; i < n; i++) // index
for (const int *p = arr; p < arr + n; p++) // pointer walk
for (;;) { if (eof) break; } // forever + break

短絡評価(ショートサーキット)

&& と || は左から右へ評価され、結果が確定した時点で停止する。これが ptr && ptr->x が安全な理由である:第2オペランドは第1が真のときにのみ実行されるため、行内でデリファレンスをガードできる。

1
2
if (ptr && ptr->val > 0) // safe: short-circuits on NULL
if (a || b) // b not evaluated if a is true

6.関数と可変長引数

値渡し vs ポインタ渡し、関数ポインタと typedef、可変長引数、再帰、_Generic。

値渡し — 呼び出し元の変数は不変

C のすべての引数は値渡しである。呼び出された関数が受け取るのはコピーであり、それを変更しても呼び出し元には影響しない。単純なスカラーや小さな構造体はこれで渡す。変更が必要、あるいは大きなオブジェクトが心配な場合はポインタ形式を使う。

1
int add(int a, int b) { return a + b; }

ポインタ渡し(呼び出し元を変更する)

呼び出し元の変数を変更したければ、そのアドレスを渡す。呼び出された関数がポインタをデリファレンスして書き込めば、変更は呼び出し元から見える。scanf のような関数がまさにこの方法で出力を生成している。

1
2
3
4
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
swap(&x, &y);

const 引数 — 読み取り専用の契約

const char* s のような const 引数は読み取り専用の契約である:関数はその指す内容を変更しないと約束する。これにより呼び出し側は文字列リテラルを安全に渡せるようになり、API の意図も明確になる。

1
2
size_t len(const char *s) { return strlen(s); }
// Also lets callers pass string literals.

配列引数の decay(長さも渡す必要あり)

配列引数はポインタに decay し、長さは一緒に渡されない。長さは必ず明示的に渡すこと。さもないと関数はデータの終わりを判断できない。

1
2
3
4
5
int sum(const int *xs, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += xs[i];
return total;
}

関数ポインタ + typedef

関数ポインタのシグネチャに一度 typedef を付けておき、以降は別名を変数や引数の宣言に使う。これでディスパッチテーブルやコールバックが、括弧とアスタリスクの壁のように読まずに済む。

1
2
3
4
5
6
7
8
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
typedef int (*BinOp)(int, int);
int apply(BinOp op, int a, int b) { return op(a, b); }
int r = apply(add, 3, 4);
// Table of function pointers (mini dispatch):
BinOp ops[] = { add, mul };
int s = ops[1](3, 4); // 12

可変引数 — printf 風関数

... 付きのシグネチャを持つ関数は printf のように可変個の引数を受け取る。va_start / va_arg / va_end で読み出す。固定引数で後続の引数の数を明示しないと、呼び出しを安全に解釈できない。

1
2
3
4
5
6
7
8
9
10
11
#include <stdarg.h>
#include <stdio.h>
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);
va_end(ap); // must pair with va_start
return total;
}
int n = sum(4, 10, 20, 30, 40); // 100

可変引数の転送 — v* 系関数

可変引数を別の可変引数関数(たとえばログラッパから vfprintf)に転送するには、v* 系を使い va_list をそのまま渡す。va_arg で再読込みするとリストが破壊される。

1
2
3
4
5
6
void logf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}

再帰

再帰関数はより小さな同種の問題で自身を呼び出し、基底条件に達するまで続ける。呼び出しごとにスタックを消費するため、深度が大きいまたは制御不能な場合は反復を選ぶ。

1
2
3
4
5
6
7
int fact(int n) { return n < 2 ? 1 : n * fact(n - 1); }
// Iterative is usually safer (no stack overflow):
long fib_iter(int n) {
long a = 0, b = 1;
for (int i = 0; i < n; i++) { long t = a; a = b; b = t + b; }
return a;
}

_Generic(C11+) — コンパイル時型ディスパッチ

_Generic(C11)は制御式の型に応じてコンパイル時に結果式を選択する。関数オーバーロードを軽量に模倣し、型安全なマクロを書く手段である。

1
2
3
4
5
6
#define abs_val(x) _Generic((x), \
int: abs_i, \
long: abs_l, \
double: abs_d, \
default: abs_x)(x)
int abs_i(int x); long abs_l(long x); double abs_d(double x);

noreturn(C11+)と inline

noreturn は決して返らない関数(致命的なエラールーチンなど)に付け、コンパイラが到達不能コードを推論できるようにする。inline は要求であって保証ではない。コンパイラは依然として実際の呼び出しを生成することがある。

1
2
3
4
5
6
#include <stdnoreturn.h>
noreturn void die(const char *msg) {
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
inline int square(int x) { return x * x; } // hint to inline

7.文字列と数値変換

NUL 終端文字列、安全な書き込み、strtol/strtok/sscanf 変換、printf フォーマット表。

基本 — NUL で終わる文字配列

C の文字列は NUL バイト('\0')で終わる文字配列である。strlen は終端文字まで数える。文字列を保持するバッファには必ずその分のスペースを確保する。さもないと書き込みがオーバーフローする。

1
2
3
4
#include <string.h>
#include <stdio.h>
char s[] = "hello"; // 6 bytes incl. '\0'
size_t n = strlen(s); // 5 (excludes '\0')

コピー / 比較 / 連結(安全版と非安全版)

strcpy / strcat は境界をチェックしないため、ソース文字列が大きすぎると静かにオーバーフローする。境界付きで常に NUL 終端される書き込みには snprintf を優先する。比較は strcmp / strncmp、strncat は最大 n 文字をコピーし終端文字を付加する。

1
2
3
4
5
6
7
strcpy(dst, src); // UNSAFE if src too big
strncpy(buf, src, sizeof buf - 1);
buf[sizeof buf - 1] = '\0'; // strncpy may not NUL-terminate
strcmp(a, b) // <0, 0, >0
strncmp(a, b, 4) // compare first 4 chars
strcat(buf, src); // appends (UNSAFE)
snprintf(buf, sizeof buf, "%s%s", a, b); // SAFE build

検索

strchr は文字の最初の出現を、strrchr は最後の出現を、strstr は部分文字列の最初の出現を探す。いずれも文字列内部を指すポインタを返し、見つからない場合は NULL を返す。

1
2
3
char *p = strchr(buf, 'w'); // first 'w'
char *q = strstr(buf, "lo wo"); // substring
char *r = strrchr(buf, 'o'); // last 'o'

mem* — 生バイト(任意のメモリを扱え、NUL も含む)

mem* 関数(memcpy、memmove、memset、memcmp)は生バイトを扱い、埋め込まれた NUL にも対応する。memcpy と memmove の違いは重なりの許容で、重なる可能性がある場合は memmove を使う。

1
2
3
4
memcpy(dst, src, n); // no overlap allowed
memmove(dst, src, n); // safe if overlapping
memset(arr, 0, sizeof arr);
int eq = memcmp(a, b, n);

数値変換:strtol / strtod

数値の解析には strtol / strtod を使う。これらはエラーを報告し、解析が止まった位置を示す。atoi は不正入力に対して黙って 0 を返すため違法入力を隠す。正確さを求めるコードでは使うべきではない。

1
2
3
4
5
6
7
#include <stdlib.h>
char *end;
errno = 0;
long v = strtol("42abc", &end, 10); // base 10 -> 42, end -> "abc"
long h = strtoll("0x1F", NULL, 16); // hex -> 31
double d = strtod("3.14e2", NULL); // -> 314.0
// atoi has no error detection: atoi("abc") == 0 silently

堅牢な解析 — 文字列全体を検証する

完全な解析では 3 つのことを検証する必要がある。strtol 後に errno がセットされていないこと、最低 1 桁が消費されたこと(end != s)、文字列全体を読み終えたこと(*end == '\0')。どれか一つでも省略すれば末尾のゴミが入り込む。

1
2
3
4
5
6
7
8
9
int parse_int(const char *s, long *out) {
if (!s || !out) return -1;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10);
if (errno || end == s || *end != '\0') return -1;
*out = v;
return 0;
}

トークン化:strtok_r(再入可能)

strtok は文字列をその場で変更し、区切り文字ごとに '\0' を挿入する。_r 接尾辞付きの版は再入可能(スレッドセーフ)である。元の文字列もまだ必要なら、まずコピーを取ること。

1
2
3
4
5
char line[] = "a,b,c";
char *save = NULL;
for (char *tok = strtok_r(line, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
puts(tok);
}

sscanf — フォーマット文字列で解析(一致数を返す)

sscanf はフォーマット文字列で文字列を解析し、成功したフィールドの一致数を返す。この戻り値を確認すること。期待より少なければ、入力は想定していた形ではない。

1
2
3
int year, month;
if (sscanf("2026-08", "%d-%d", &year, &month) == 2) { /* ok */ }
// %d %f %s %c %zu %x %n (%% = literal %)

printf フォーマット一覧

printf ファミリーは共通のフォーマット言語を共有する:%d int、%ld long、%zu size_t、%f double、%s 文字列、%p ポインタに幅や精度の修飾子を加えたもの。フォーマット指定子は実引数の型と厳密に一致させる必要がある。一致しない場合は未定義動作である。

1
2
3
4
5
6
7
8
// %d int %ld long %lld long long
// %u unsigned int %zu size_t %zd ssize_t
// %f double %lf double %e scientific
// %.2f 2 decimals %x / %X hex %o octal
// %c char %s string %p pointer
// %5d pad to 5 width %-5d left-justify %05d zero-pad
printf("%s=%d\n", "count", 42);
printf("%.2f %%\n", 3.14159); // 3.14 % (%% = literal %)

安全に文字列を連結する

snprintf は最大 n バイトを書き、常に NUL で終端するため、安全に文字列を連結する手段である。増え続けるテキストに対しては長さを記録しながらバッファを realloc するか、小さな動的バッファヘルパーを使う。

1
2
3
4
char buf[64];
snprintf(buf, sizeof buf, "user-%d", id);
// Grow-able: use strcat carefully or implement a dynamic buffer.
// For heavy string work consider OpenSSL BIO or a small dyn-str helper.

8.配列・ソート・検索

配列の反復、比較関数付き qsort / bsearch、二次元配列、構造体の配列。

固定長配列 — 長さを指定して反復

C の配列は固定サイズの連続した要素の塊である。長さを使って反復する。本物の配列がスコープ内にある間は sizeof a / sizeof *a が有効だが、関数に渡した瞬間に大きさは失われる。

1
2
3
4
int arr[5] = { 3, 1, 4, 1, 5 };
for (size_t i = 0; i < sizeof arr / sizeof *arr; i++) {
printf("%d ", arr[i]);
}

初期化パターン

配列は位置、指定したインデックス([2] = 9)、または全体ゼロクリア({0})で初期化できる。二次元配列は配列の配列であり、行優先で格納される。m[i][j] は各行内で連続している。

1
2
3
int zeros[10] = { 0 }; // all zero
int spec[5] = { [0] = 9, [4] = 1 }; // designated
int grid[2][3] = { {1,2,3}, {4,5,6} }; // 2-D

qsort — 比較関数でソート

qsort は比較関数でインプレースソートを行い、比較関数は <0、0、>0 のいずれかを返す。比較関数には要素へのポインタが渡されるため、void* 引数を要素型に戻してから安全に比較する(オーバーフロー回避)。

1
2
3
4
5
6
7
8
9
#include <stdlib.h>
#include <stdio.h>
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y); // safe for big/small ints
}
int nums[] = { 5, 2, 9, 1 };
qsort(nums, 4, sizeof(int), cmp_int); // 1 2 5 9

bsearch — ソート済み配列での二分探索

bsearch は二分探索を行うが、ソート済みの配列に対してのみ機能する。一致した要素へのポインタを返し、見つからない場合は NULL を返す。大きな配列ではソートしてから検索するほうが線形スキャンより高速。

1
2
3
int key = 5;
int *hit = bsearch(&key, nums, 4, sizeof(int), cmp_int);
if (hit) printf("found %d\n", *hit);

文字列比較関数(char* の配列)

char* の配列をソートするにはポインタ値ではなく文字列を比較する必要がある。比較関数を char* const* にキャストし、デリファレンスして各文字列ポインタを取得してから strcmp を呼ぶ。

1
2
3
4
5
6
7
int cmp_str(const void *a, const void *b) {
const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
return strcmp(x, y);
}
const char *names[] = { "bob", "alice" };
qsort(names, 2, sizeof(char*), cmp_str);

構造体の配列 + フィールドでソート

構造体の配列をフィールドでソートするには、2 つの構造体ポインタを受け取りそのフィールドを比較する比較関数(たとえば a->key と b->key の三方比較)を書く。qsort がレコード全体を並び替えてくれる。

1
2
3
4
5
6
7
struct Item { int key; const char *name; };
int cmp_item(const void *a, const void *b) {
const struct Item *x = a, *y = b;
return (x->key > y->key) - (x->key < y->key);
}
struct Item items[] = { {2,"b"}, {1,"a"}, {3,"c"} };
qsort(items, 3, sizeof(struct Item), cmp_item);

二次元配列ポインタ引数(二次元形状を保つ)

二次元配列を関数に渡すには、引数型に列数を含めなければならない。int m[][COLS] のように。これでコンパイラは各行の開始位置を計算できる。第 1 次元(行数)は自由に変化できる。

1
void print_grid(int rows, int cols, int m[][cols]);

動的配列パターン(mem の節も参照)

拡張可能なコレクションは malloc したブロックであり、満杯になったら realloc する。容量と長さは別々に保持し、伸長時に 2 倍にし、使い終わったら free する。高級言語での vector / list に相当するものを C で実現する方法である。

1
2
3
4
5
6
#include <stdlib.h>
int *dyn = malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) dyn[i] = i;
// grow:
dyn = realloc(dyn, 20 * sizeof(int));
free(dyn);

可変長配列(C99、オプション)

可変長配列は実行時の値でスタック上に確保される(int vla[n])。C11 ではこれはオプションであり、n が大きすぎるとスタックを破裂させる可能性がある。大きいもの、ポータビリティが必要なものでは malloc を優先する。

1
// int n = 8; int vla[n]; // stack array sized at runtime

二次元配列 — 行優先

int m[ROWS][COLS] は連続した行優先のブロックである。m[i][j] は *(*(m + i) + j) に等しい。インデックスは各行の長さを必要とし、行の開始位置を計算するため、形状はコンパイル時に確定していなければならない。

1
2
3
4
5
6
7
#define ROWS 2
#define COLS 3
int m[ROWS][COLS] = { {1,2,3}, {4,5,6} };
for (int i = 0; i < ROWS; i++)
for (int j = 0; j < COLS; j++)
printf("%d ", m[i][j]);
// Contiguous: m[i][j] == *(*(m + i) + j)

9.動的メモリと所有権

malloc/calloc/realloc/free、スタック vs ヒープ、アライメント、所有権のルール、goto cleanup パターン。

確保と解放

malloc は未初期化のヒープメモリを返し、確保したブロックはすべて最終的に解放しなければならない。使用前に必ず NULL かどうかを確認する。解放後はポインタを NULL にセットし、その後の二重解放は安全な no-op とする。

1
2
3
4
5
6
#include <stdlib.h>
#include <string.h>
int *p = malloc(10 * sizeof(int)); // uninitialized
if (!p) { perror("malloc"); exit(EXIT_FAILURE); } // ALWAYS check
free(p); // after free, p is dangling
p = NULL; // avoid use-after-free

calloc:count × size のゼロクリア

calloc(count, size) はメモリをゼロクリアし、2 つの引数を掛け合わせ、オーバーフローも検出する。初期化済みストレージが必要で要素数が分かっている場合はこちらを優先する。

1
int *z = calloc(10, sizeof(int)); // all zeros

realloc — サイズ変更(メモリブロックが移動する可能性あり)

realloc は確保サイズを変更し、ブロックを新しいアドレスへ移動させる可能性がある。戻り値を一時変数で受け取り確認する。失敗時には古いブロックが依然有効だが、ポインタを先に上書きするとリークする。

1
2
3
4
5
int *q = malloc(4 * sizeof(int));
int *tmp = realloc(q, 8 * sizeof(int));
if (tmp) { q = tmp; } // on failure old block still valid
else { /* keep q, handle error */ }
free(q);

スタック vs ヒープ

スタックメモリは自動管理で高速、関数から戻ると回収されるが、小さく短命である。ヒープメモリは手動管理で大きく、関数を越えて生存できる。大きく長寿命なバッファはヒープに置くべきである。

1
2
3
// Stack: fast, auto-freed, small (MBs), fixed size at compile/runtime
// Heap: large, manual free, survives return, slower
// int local[1'000'000]; may overflow the stack -> use malloc

所有権 — シンプルなルール一式

最小限の所有権ルールを守るだけでメモリ問題を抑えられる。確保した側が解放する。ポインタは 1 回だけ解放する。解放後は NULL をセットする。realloc 失敗時は古いポインタを保持する。規約こそが C における唯一のリソースマネージャである。

1
2
3
4
// 1) Whoever allocates frees (or documents otherwise)
// 2) Free once, never free a non-heap pointer
// 3) After free set pointer to NULL to catch double-free
// 4) Realloc failure: keep the old pointer

ヒープ上の文字列

ヒープ上の文字列には strlen(s) + 1 バイトが必要で、その +1 は NUL 終端文字の分である。malloc(strlen(s) + 1) してからコピーする。+1 を忘れると起爆待ちの 1 バイトオーバーフローになる。

1
2
3
char *name = malloc(strlen("hello") + 1);
strcpy(name, "hello");
free(name);

内部ポインタを持つ struct

ヒープデータを所有する struct は解放すべき 2 つの確保を持つ。最初に内部バッファを、次に struct 自体を解放する。内部ポインタの所有者を決定して明示し、確保の逆順で解放する。

1
2
3
4
5
6
7
struct Line { char *data; size_t len; };
struct Line *line = malloc(sizeof *line);
line->data = malloc(100);
line->len = 0;
// free order: innermost first
free(line->data);
free(line);

goto cleanup(単一出口)

C にはデストラクタがないため、goto-cleanup が C 版の RAII である。関数ごとに 1 つの出口と 1 つのクリーンアップブロックを持ち、すべてのパスがそこに到達する。各リソースはすべてのパスでちょうど 1 回解放される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int process(const char *path) {
FILE *f = NULL;
char *buf = NULL;
int rc = -1;
f = fopen(path, "r");
if (!f) { perror("fopen"); goto cleanup; }
buf = malloc(4096);
if (!buf) { perror("malloc"); goto cleanup; }
if (fread(buf, 1, 4096, f) == 0) goto cleanup;
// ... work ...
rc = 0;
cleanup:
free(buf);
if (f) fclose(f);
return rc; // single return
}

アラインメント / 過剰アラインメント(C11)

aligned_alloc は指定したアラインメント(SIMD、キャッシュラインの共有など)でメモリを確保でき、サイズはアラインメント値の倍数でなければならない。通常の malloc はプラットフォームのデフォルトアラインメントのみを保証する。

1
2
3
#include <stdlib.h>
// aligned_alloc(size, alignment) — size must be a multiple of alignment
// double *a = aligned_alloc(alignof(double) * 4, alignof(double) * 4);

メモリリーク / オーバーフロー検出ツール(build の節を参照)

リーク、オーバーフロー、use-after-free はすべてプロセスが死ぬまで隠れる。valgrind または AddressSanitizer で機械的に検出する。どちらも build の節で扱っており、追加できる最も安価な安全網である。

1
2
3
// valgrind ./prog
// gcc -fsanitize=address,undefined -g prog.c -o prog
// AddressSanitizer catches leaks, OOB, use-after-free at runtime

10.オブジェクト風パターン

C は手続き型ですが、構造体と関数ポインタでクラス、継承、多態をエミュレートできます。

「クラス」 = struct + vtable

C でクラスを模倣する。struct にデータを入れ、関数ポインタテーブル(vtable)に振る舞いを入れる。メソッドは明示的な self / this ポインタを引数に取る通常の関数である。

1
2
3
4
5
6
7
8
9
struct Animal {
const char *name;
void (*speak)(const struct Animal *self);
};
static void dog_speak(const struct Animal *a) {
printf("%s: woof\n", a->name);
}
struct Animal rex = { "Rex", dog_speak };
rex.speak(&rex);

埋め込みによる継承の実現

基底クラスの struct を最初のメンバーとして埋め込めば継承になる。派生 struct のアドレスは基底クラスのアドレスと等しいので、基底クラスを期待するあらゆる場所で派生値を使え、基底クラスのメソッドはそのまま機能する。

1
2
3
struct Dog { struct Animal base; int breed; };
struct Dog d = { { "Rex", dog_speak }, 1 };
d.base.speak(&d.base);

コンストラクタ / デストラクタのペア

確保と初期化を担う init 関数と、解放と後始末を担う free 関数をペアにする。これが C 版のコンストラクタ / デストラクタである。作成された各オブジェクトは必ず 1 回だけ破棄しなければならない。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Vec {
int *data;
size_t len, cap;
};
struct Vec *vec_new(void) {
struct Vec *v = malloc(sizeof *v);
if (v) { v->data = NULL; v->len = v->cap = 0; }
return v;
}
void vec_free(struct Vec *v) {
free(v->data);
free(v);
}

opaque ポインタ — 実装を隠す(PIMPL)

ヘッダファイルでは struct の前方宣言のみとし、定義は .c ファイルにだけ置く。呼び出し側はポインタを保持できるが内部は見えない。これが PIMPL であり、実装を隠して内部の変更が呼び出し側を破壊しないようにする。

1
2
3
4
5
6
7
8
9
// foo.h:
struct Foo; // forward declaration
typedef struct Foo Foo;
Foo *foo_new(void);
void foo_destroy(Foo *f);
int foo_get(const Foo *f);
// foo.c:
struct Foo { int value; }; // definition hidden
Foo *foo_new(void) { return calloc(1, sizeof(struct Foo)); }

インターフェース = 関数ポインタで構成された struct

関数ポインタで満たされた struct がインターフェースである。それを満たすどんな実装でも差し込め、各関数はインスタンスのコンテキストとして void* self を引数に取る。これが C 形態のポリモーフィズムである。

1
2
3
4
5
struct Stream {
int (*read)(void *self, char *buf, int n);
int (*write)(void *self, const char *buf, int n);
void *self; // context
};

型消去データ(void *)

void* は型を消し去り、1 つのコンテナに異種の値を入れられるようにする。型を安全に戻すには、通常、ポインタが実際に何を指しているかを記録するタグフィールドを追加する必要がある。

1
2
typedef struct { void *data; } Box;
// + a tag for runtime type checks if needed

*this を返すことによるメソッドチェーンの実現

各 setter はオブジェクト自身(*this)を返し、呼び出しを連鎖できる。builder->setA(x)->setB(y) のように。builder 形式の設定を簡潔にするが、戻り値が無視される可能性がある点を代償とする。

1
2
3
4
struct Builder *set_name(struct Builder *b, const char *n) {
b->name = n; return b;
}
set_name(set_name(new_builder(), "app"), "x");

シングルトン(遅延初期化、スレッドセーフではない)

遅延初期化シングルトンは最初の使用時にインスタンスをstaticポインタに割り当てる。このシンプルなバージョンはスレッドセーフではなく、二つのスレッドが最初の割り当てを競合する可能性があり、共有する場合はロックが必要。

1
2
3
4
5
static Config *cfg = NULL;
Config *get_config(void) {
if (!cfg) cfg = config_new();
return cfg;
}

参照カウント(シンプル版)

retain/release はオブジェクトに参照カウントを割り当て、ゼロになった時点で解放する。これは Objective-C の基礎であり、C のオブジェクトにとってはガベージコレクションより軽量な所有権モデル。

1
2
void retain(Object *o) { o->refs++; }
void release(Object *o) { if (--o->refs == 0) free(o); }

完全な OOP も可能(glib)

C には完全な OOP フレームワークが存在する——glib と GObject がその証拠——ただし実際の複雑さも伴う。最小限の vtable と不透明ポインタでほとんどの要件をカバーできる。重厚な武器は真に必要なプロジェクトに残す。

1
2
// Prefer a minimal vtable + opaque pointer.
// For callbacks: use void* context args to avoid globals.

11.エラー処理

例外はありません:errno、out パラメータ付き戻り値、goto cleanup、エラー列挙型、アサーション。

呼び出し失敗後の errno

ライブラリ関数やシステム関数は失敗時にグローバル errno をセットし、呼び出し直後に読むかコピーしなければならない——後続の呼び出しが上書きする可能性がある。strerror(errno) はエラーコードを読めるメッセージに変換する。

1
2
3
4
5
6
7
8
9
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
if (mkdir("foo", 0755) != 0) {
fprintf(stderr, "mkdir: %s\n", strerror(errno));
return 1;
}
// Always read errno immediately after the call (or copy it).

リターンコード + 出力引数(一般的な C の慣用句)

C の主流なエラー慣用句:成功時に 0 を返し、失敗時に負のエラーコードを返し、結果は出力引数のポインタで書き出す。失敗時には結果ポインタは NULL/不変のままで、呼び出し元はこれで判定できる。

1
2
3
4
5
6
7
8
9
10
11
12
int parse_int(const char *s, long *out) {
if (!s || !out) return -1;
errno = 0;
char *end = NULL;
long v = strtol(s, &end, 10);
if (errno || end == s || *end != '\0') return -1;
*out = v;
return 0; // 0 == success
}
long n;
if (parse_int("42", &n) == 0) printf("got %ld\n", n);
else fprintf(stderr, "bad input\n");

エラー列挙——明示的なエラー分類

関数が生成する可能性のあるエラーの種類——OK、INVALID_INPUT、NOMEM、IO——を列挙し、それを返す。名前付きエラーコードはドキュメントとして機能し、switch に適し、あらゆる失敗パターンを明確に考えることを促す。

1
2
3
4
5
enum Error { ERR_OK = 0, ERR_INVALID, ERR_NOMEM, ERR_IO };
enum Error do_thing(int x) {
if (x < 0) return ERR_INVALID;
return ERR_OK;
}

assert——デバッグ用(NDEBUG で削除)

assert(cond) は cond が偽のときプログラムを中断し、開発期にバグを捕捉する。-DNDEBUG でコンパイルされなくなるため、ユーザーが依存する動作のガードには決して使ってはならない——真と仮定している不変条件のみを検証する。

1
2
3
#include <assert.h>
assert(ptr != NULL);
assert(index < size && "index out of range");

perror——errno の説明を出力

perror はあなたのメッセージと現在の errno の説明を stderr に 1 行で出力する。これはシステムコール失敗の理由を報告する最も速い方法——errno の部分は自分で書式文字列を書く必要がない。

1
2
FILE *f = fopen(path, "r");
if (!f) { perror(path); return 1; } // 'path: No such file'

_Static_assert——コンパイル時

_Static_assert は実行時ではなくコンパイル時に前提を検証する。これを使ってサイズ、オフセット、ABI 制約を検証し、本番バイナリで静かに失敗することを防ぐ。

1
_Static_assert(sizeof(int) == 4, "expected 4-byte int");

スレッドセーフな strerror_r(POSIX)

strerror はスレッドセーフではなく、内部バッファを上書きする。strerror_r(POSIX)は呼び出し側が提供したバッファに書き込むため、並行スレッドはそれぞれのメッセージを取得できる。

1
2
3
char ebuf[256];
strerror_r(errno, ebuf, sizeof ebuf);
fprintf(stderr, "err: %s\n", ebuf);

システムコールの戻り値を確認する

システムコールは失敗する——ファイル、ソケット、メモリのいずれも。戻り値を無視すると、軽微な失敗が後になってクラッシュやデータ破損に変わる。fread/fwrite は成功しても短いカウントを返す可能性があるため、必ず確認すること。

1
2
3
4
5
int fd = open(path, O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
// Check fread/fwrite: count may be short on errors
size_t got = fread(buf, 1, sizeof buf, f);
if (got < sizeof buf && ferror(f)) { /* real error */ }

ferror/feof でエラーとファイル終端を区別する

読み込みループの終了時、ferror() と feof() が理由を教えてくれる——真の I/O エラーか、ファイル終端に到達したか。EOF はエラーではなく正常な状態——ferror で両者を区別する。

1
2
if (ferror(f)) puts("read error");
else if (feof(f)) puts("end of file");

12.ファイル I/O

stdio ストリーム、行・ファイル全体の読み込み、バイナリ I/O、POSIX ディスクリプタ、mmap、ディレクトリ走査。

オープン / クローズ

fopen は FILE* ストリームを返し、失敗時は NULL を返す。fclose はフラッシュして解放する。モードは r/w/a に + を加えると読み書き両用、b を加えるとバイナリ。オープン失敗後にそのポインタを使い続けてはならない。

1
2
3
4
5
#include <stdio.h>
FILE *f = fopen("data.txt", "r");
if (!f) { perror("fopen"); return 1; }
// modes: "r" "w" "a" "r+" "w+" "a+" (+"b" for binary: "rb" "wb")
fclose(f);

ファイル全体を読み込む(小ファイル)

小ファイルの手順:末尾に seek し、ftell でサイズを取得し、rewind し、malloc で size+1 を確保し、fread で全部読む。読み込まれたバイト数と要求が一致することを確認——呼び出しの間にファイルが変更されている可能性がある。

1
2
3
4
5
6
7
fseek(f, 0, SEEK_END);
long sz = ftell(f);
rewind(f);
char *buf = malloc(sz + 1);
if (buf && fread(buf, 1, sz, f) == (size_t)sz) {
buf[sz] = '\0';
}

1 行ずつ読み込み(慣用句)

fgets は 1 行を固定バッファに安全に読み込み、改行文字またはバッファ上限で停止する。末尾の改行を取り除くには strcspn を使う——改行はバッファに残される。これはテキストを 1 行ずつ処理する慣用的な方法。

1
2
3
4
5
char line[256];
while (fgets(line, sizeof line, f)) {
line[strcspn(line, "\r\n")] = '\0'; // strip trailing newline
process(line);
}

書き込み

fprintf/fputs/fputc はそれぞれ書式付き出力、文字列、文字出力を行う。エラーは書き込み時にも fclose 時にも発生するため、fclose の結果を確認し、見落とされたフラッシュの失敗を防ぐ。

1
2
3
fprintf(f, "%s=%d\n", "key", 42);
fputs("line\n", f);
fputc('x', f);

先に文字列にフォーマットする(安全)

先に snprintf で出力全体をバッファに組み立て、一度に書き出す。これで並行ソースからの交错した書き込みを避け、書き込み前に検証でき、実際にも書き込みのアトミック性を維持できる。

1
2
3
char out[64];
snprintf(out, sizeof out, "user-%d", id);
fputs(out, f);

バイナリ I/O——生バイトの読み書き

fread/fwrite は生バイトを扱い、固定長レコードに最適:ちょうど sizeof(record) を読み、戻り値を処理する。短い読み込みはエラーか EOF——ferror/feof で区別する。

1
2
3
4
uint32_t header[4];
size_t n = fread(header, sizeof header[0], 4, f);
if (n != 4) { /* short read: ferror(f) or feof(f) */ }
fwrite(header, sizeof header[0], 4, out);

POSIX ファイルディスクリプタ

stdio の下に、Linux/macOS は int ファイルディスクリプタ用の open/read/write/close を提供する:細かい制御が可能でバッファリングなし、落とし穴も多い。境界を越えるには fdopen(fd を FILE* に)または fileno(FILE* を fd に)を使う。

1
2
3
4
5
6
7
#include <unistd.h>
#include <fcntl.h>
int fd = open("data.bin", O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
char b[4096];
ssize_t got = read(fd, b, sizeof b); // may be < sizeof b
close(fd);

mmap——ファイルをメモリにマップする

mmap はファイルをアドレス空間に直接マップし、読み書きが通常のメモリアクセスになる——大ファイルで高速。munmap で解放する。ファイルは空ではならず、マップサイズはページ境界に揃える。

1
2
3
4
5
6
7
8
9
#include <sys/mman.h>
#include <sys/stat.h>
int fd = open("big.bin", O_RDONLY);
struct stat st; fstat(fd, &st);
const void *data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) { perror("mmap"); close(fd); return 1; }
// use data[0..st.st_size-1] ...
munmap((void *)data, st.st_size);
close(fd);

ディレクトリの走査

opendir/readdir/closedir はディレクトリ項目を一つずつ列挙する。暗黙の「.」と「..」エントリをスキップし、stat で各名前のファイルタイプとサイズを調べる。

1
2
3
4
5
6
7
#include <dirent.h>
DIR *d = opendir(".");
struct dirent *e;
while ((e = readdir(d))) {
if (e->d_name[0] != '.') printf("%s\n", e->d_name);
}
closedir(d);

stat——ファイルのメタデータ

stat/lstat/fstat はファイルのメタデータ——サイズ、更新時刻、パーミッション、タイプ——を返す。tmpfile() は匿名のテンポラリファイルを提供し、クローズ時に自動削除される。一時データの保存に適している。

1
2
3
4
5
6
7
8
9
10
#include <sys/stat.h>
struct stat st2;
if (stat("data.txt", &st2) == 0) {
printf("size=%ld mtime=%ld\n", (long)st2.st_size, (long)st2.st_mtime);
}
// tmpfile(): unnamed temp file, auto-deleted on close
FILE *t = tmpfile();
fprintf(t, "scratch\n");
rewind(t);
fclose(t);

13.よくある落とし穴

定番の C の罠 10 個 —— 正しい書き方には GOOD、間違った書き方には BAD の印が付きます。

バッファオーバーフロー

固定バッファの末尾を超えて書き込むと隣接メモリを破損させ、最高レベルのセキュリティ脆弱性となる。すべての書き込みに上限を設定:strncpy で手動の NUL 補完、snprintf、または長さ指定の fgets。サニタイザは人目では見逃す問題を捕捉できる。

1
2
3
4
5
char buf[10];
strcpy(buf, src); // BAD — writes past buf if src is long
strncpy(buf, src, sizeof buf - 1);
buf[sizeof buf - 1] = '\0'; // GOOD — bounded, NUL-terminated
snprintf(buf, sizeof buf, "%s", src); // GOOD — simplest

解放後の使用 / 二重解放

解放後の使用は未定義動作であり、しばしば悪用可能。同じメモリを 2 回解放するとアロケータが破壊される。free 後すぐにポインタを NULL に設定する——free(NULL) は安全な何もしない操作。

1
2
3
4
5
int *p = malloc(sizeof(int));
free(p);
*p = 42; // BAD — dangling pointer (UB)
p = NULL; // GOOD — marks it freed
free(p); // safe (free(NULL) is a no-op)

メモリリーク

すべての割り当てをすべてのパスで解放しなければならない。リークは静かに膨らみ、プロセスが終了するまで続く。AddressSanitizer と valgrind はデフォルトで報告する——クラッシュした後ではなく、開発期に実行する。

1
2
3
int *p = malloc(4 * sizeof(int));
return; // BAD — leaks
free(p); // GOOD — before every return

= と ==

単一の = は代入;== は比較。if (x = 0) は 0 を代入し常に偽となる——静かで発見しにくいバグ。-Wall でコンパイルし、条件内の代入には括弧を付ける。

1
2
3
if (x = 0) { /* ... */ } // BAD — assigns, condition is false
if (x == 0) { /* ... */ } // GOOD
if ((x = foo()) != 0) { /* ... */ } // GOOD — assign in condition with parens

符号付きオーバーフローは UB

符号付き整数オーバーフローは未定義動作:コンパイラはそれが決して発生しないと仮定して最適化する可能性がある。大きな符号付き値を加算する前に long long に拡張するか、本来意図する符号なしラップアラウンドを使う。

1
2
3
int a = 2000000000, b = 2000000000;
int c = a + b; // BAD — signed overflow = UB
long long c2 = (long long)a + b; // GOOD — widen first

未初期化変数

未初期化のローカル変数の読み取りは未定義動作で、通常はゴミの値を取得する。宣言時にすべての変数を初期化する——コンパイラの -Wuninitialized が初めて意味を持つ。

1
2
3
int x;
printf("%d\n", x); // BAD — indeterminate value
int x = 0; // GOOD — always initialize

sizeof における配列の decay

関数内部では配列パラメータはポインタとなり、sizeof は 8(ポインタサイズ)を返し、配列サイズではない。sizeof a / sizeof a[0] は本物の配列がスコープ内にある場合のみ有効——明示的に長さを渡す。

1
2
3
4
5
void f(int arr[10]) {
sizeof(arr); // BAD — size of pointer (8), not 40
}
// GOOD — pass the length explicitly:
void g(const int *arr, size_t n);

off-by-one エラー

ループ境界は古典的な off-by-one エラー:n 個の要素を i < n(インデックス 0..n-1)で反復すべきで、i <= n のように 1 つ多く触れて境界外にはならない。サイズ n の配列の最後の有効なインデックスは n-1。

1
2
for (int i = 0; i <= n; i++) // BAD — runs n+1 times
for (int i = 0; i < n; i++) // GOOD

printf の書式不一致

書式指定子と引数の型の不一致は未定義動作——%s に int を渡すと直接クラッシュする可能性。size_t には %zu、%d/%ld/%lld は型を一致させ、-Wformat を有効にして不一致を捕捉する。

1
2
3
printf("%s\n", x); // BAD — %s expects char*, x is int
printf("%d\n", x); // GOOD
printf("%zu\n", sizeof x); // GOOD — size_t needs %zu

符号付き / 符号なし比較

符号付きと符号なしの比較では、符号付きオペランドは符号なしに昇格され、負の値は非常に大きな正の値になる。両側を明示的に同じ符号付き型にキャストするか、そもそも混在させない。

1
2
3
4
5
int i = -1;
unsigned u = 0;
if (i < u) { /* ... */ } // BAD — i promoted to unsigned, huge
if ((long long)i < (long long)u) { /* */ } // GOOD — compare as signed
// Also: (int)1 < (unsigned)-1 is FALSE — surprising, use explicit casts

14.スレッドと並行処理

pthread create/join、mutex、条件変数、アトミック、C11 スレッド —— ビルドとリンクに -pthread を使用。

ビルドとヘッダファイル

スレッドプログラムは -pthread でコンパイルし、pthread ライブラリをリンクする。<pthread.h> をインクルードし、各 pthread_* の戻り値を確認するエラーとして扱う——これらの API は errno を設定しない。

1
2
3
// Build: $ gcc -pthread prog.c -o prog
#include <pthread.h>
#include <stdio.h>

作成と待機

pthread_create はある関数を実行するスレッドを起動し、その関数は void* 引数を受け取る。pthread_join はその終了をブロックして待機する。渡された引数と返される void* がスレッドを越えてデータを渡すチャネルとなる。

1
2
3
4
5
6
7
8
9
void *worker(void *arg) {
int id = *(int *)arg;
printf("thread %d\n", id);
return NULL;
}
pthread_t t;
int id = 1;
pthread_create(&t, NULL, worker, &id);
pthread_join(t, NULL); // wait for t

void* でデータを返す

スレッドは結果を void* で返す——通常は malloc された値へのポインタ。呼び出し側は join 後に返されたポインタをキャストし直して値を読み出し、解放する。

1
2
3
4
5
6
7
8
9
10
11
void *compute(void *arg) {
int *out = malloc(sizeof(int));
*out = 42;
return out;
}
pthread_t t2;
pthread_create(&t2, NULL, compute, NULL);
void *res;
pthread_join(t2, &res);
int value = *(int *)res; // 42
free(res);

ミューテックス——共有状態の保護

ミューテックスは共有状態へのアクセスを直列化する:アクセス前にロックし、後にアンロックする。クリティカルセクションは短く保つ——ロックはボトルネックであり、長時間保持するとデッドロックを招く。

1
2
3
4
5
6
7
8
9
10
11
12
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
long shared = 0;
void *inc(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mtx);
shared++;
pthread_mutex_unlock(&mtx);
}
return NULL;
}
// init at runtime with attributes:
// pthread_mutex_init(&mtx, NULL);

条件変数——待機 / 通知

条件変数は、別スレッドが述語の変化を通知するまでスレッドをスリープさせる。常に while ループ内で待機(偽の起床がある)し、ミューテックスを保持し、目覚めたら述語を再確認する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pthread_mutex_t cv_m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
int ready = 0;
// Producer:
void *produce(void *a) {
pthread_mutex_lock(&cv_m);
ready = 1;
pthread_cond_signal(&cv); // wake one waiter
pthread_mutex_unlock(&cv_m);
return NULL;
}
// Consumer:
void *consume(void *a) {
pthread_mutex_lock(&cv_m);
while (!ready) // loop — spurious wakeups
pthread_cond_wait(&cv, &cv_m);
pthread_mutex_unlock(&cv_m);
return NULL;
}

デタッチスレッド——join 不要

デタッチスレッドは終了時に自らクリーンアップし、join 不要。決して待機しないと確定した場合のみデタッチする。また、呼び出し側スタックへのポインタをデタッチスレッドに渡してはならない——その時点でスタックは既に消えている可能性がある。

1
2
3
4
5
6
pthread_t t3;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&t3, &attr, worker, NULL);
pthread_attr_destroy(&attr);

C11 スレッド(ポータブル)

C11 の <threads.h>(thrd_t)は pthreads より薄く、ポータブルな代替——機能は弱く、多くの場合 OS API の薄いラッパーにすぎない。POSIX で本格的なことを行うには pthreads が依然として標準の選択肢。

1
2
3
4
5
// #include <threads.h>
// int run(void *a) { return 0; }
// thrd_t th;
// thrd_create(&th, run, NULL);
// thrd_join(th, NULL);

アトミック操作(C11)——<stdatomic.h>

atomic_int と atomic_fetch_add は、定義済みメモリ順序のロックフリーなロード、ストア、インクリメントを提供する——ミューテックスより速いシンプルな共有カウンターで、通常の int より正しい(競合しない)。

1
2
3
4
5
#include <stdatomic.h>
atomic_int counter = 0;
atomic_fetch_add(&counter, 1);
int now = atomic_load(&counter);
// Lock-free check: atomic_is_lock_free(&counter)

スレッドローカルストレージ(C11)

_Thread_local(C11、GCC では __thread)は各スレッドに変数の独自のコピーを持たせる。スレッドごとのキャッシュに適し、互いに上書きしてはならないエラースロットにも適する。

1
2
3
#include <threads.h>
_Thread_local int tls_count = 0; // each thread its own copy
// GCC/Clang also: __thread

スレッドのエラー処理

pthread 関数は errno をセットするのではなく、errno スタイルのエラーコードを返す。戻り値を受け取り、strerror(rc) で診断メッセージをフォーマットする——確認されない失敗は通常は謎のハングアップやクラッシュとして現れる。

1
2
3
4
5
int rc = pthread_create(&t, NULL, worker, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_create: %s\n", strerror(rc));
}
// pthread functions return an errno-style code (not set errno)

デッドロック回避

デッドロックは「必要なものを保持しながら別で必要なものを待つ」ことから生じる。固定されたグローバル順序でロックし、クリティカルセクションをできるかぎり小さくし、優先的に 1 リソース 1 ロックとし、有界待機が必要なときはタイムアウト付きロックを使う。

1
2
3
// Lock in a fixed global order.
// Try pthread_mutex_timedlock for bounded waits.
// Prefer one lock per resource; keep critical sections short.

15.ネットワーク(ソケット)

getaddrinfo、TCP クライアントとサーバ、最小限の HTTP リクエスト —— Linux/macOS では追加ライブラリなしでリンク。

ビルドとヘッダファイル

POSIX ソケットには <sys/socket.h>、<netinet/in.h>、<arpa/inet.h> が必要。Linux/macOS では追加リンクライブラリは不要。アドレス解決には getaddrinfo を使い、sockaddr 構造体を手で埋めない。

1
2
3
4
5
6
// Build: $ gcc -std=c17 prog.c -o prog (Linux/macOS; Windows uses Winsock)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

TCP クライアント——解決、接続、送受信

TCP クライアントは getaddrinfo でサーバーを解決し、ソケットを作成し、connect する。アドレスリストは長い場合がある——順次試行し、最初に成功したものを使い、失敗したものはすぐに閉じる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int connect_tcp(const char *host, const char *port) {
struct addrinfo hints = {0}, *res;
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
int g = getaddrinfo(host, port, &hints, &res);
if (g != 0) { fprintf(stderr, "%s\n", gai_strerror(g)); return -1; }
int fd = -1;
for (struct addrinfo *ai = res; ai; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd < 0) continue;
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) break;
close(fd); fd = -1;
}
freeaddrinfo(res);
return fd;
}

送信 / 受信

send/recv は接続済みソケット上でバイトをやりとりするが、一度に全部を運ぶ保証はない。メッセージ全体を受信するか EOF に達するまでループし、残りのバイト数を追跡する。

1
2
3
4
5
6
7
int fd = connect_tcp("example.com", "80");
const char *req = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
send(fd, req, strlen(req), 0);
char buf[4096];
ssize_t n = recv(fd, buf, sizeof buf - 1, 0); // may be partial
buf[n] = '\0';
close(fd);

TCP サーバー——ソケットの流れ

サーバーはソケットを作成し、SO_REUSEADDR を設定し、ポートに bind し、listen し、accept のループに入る。accept ごとにそのクライアント専用の新しいソケットが返される——リスニングソケットは決して読み込まれない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int srv = socket(AF_INET, SOCK_STREAM, 0);
int one = 1;
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(8080) };
addr.sin_addr.s_addr = INADDR_ANY;
bind(srv, (struct sockaddr *)&addr, sizeof addr);
listen(srv, 16);
for (;;) {
int cfd = accept(srv, NULL, NULL);
char req[1024];
recv(cfd, req, sizeof req - 1, 0);
const char *resp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
send(cfd, resp, strlen(resp), 0);
close(cfd);
}

ホスト名を IP に解決

getaddrinfo はホスト名とポートを候補アドレスのリストに変換し、IPv4/IPv6 とサービス名も処理する。成功するまで順次試し、freeaddrinfo で解放する。

1
2
3
4
5
6
struct addrinfo *r;
getaddrinfo("example.com", NULL, &(struct addrinfo){.ai_family=AF_INET}, &r);
struct sockaddr_in *sa = (struct sockaddr_in *)r->ai_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof ip);
freeaddrinfo(r);

ブロッキング vs ノンブロッキング

ソケットのデフォルトはブロッキング:recv はデータが来るまで待機する。O_NONBLOCK を設定すると、準備ができていないときに EAGAIN を返す。poll/epoll/select を使うことで、シングルスレッドで多数のソケットを監視できる。

1
2
3
// fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
// Non-blocking recv returns -1 with errno==EAGAIN/EWOULDBLOCK when idle.
// For many clients: poll()/select()/epoll (Linux) — see man poll.

UDP——sendto/recvfrom(コネクションレス)

UDP はコネクションレス:sendto/recvfrom は毎回送信先アドレスを付ける必要があり、ハンドシェイクも順序も到達保証もない。遅延に敏感で損失を許容できるトラフィックに適している。

1
2
3
int us = socket(AF_INET, SOCK_DGRAM, 0);
sendto(us, "ping", 4, 0, (struct sockaddr *)&addr, sizeof addr);
recvfrom(us, buf, sizeof buf, 0, NULL, NULL);

バイト順——htons/htonl とネットワークバイト順

ネットワークバイト順はビッグエンディアン、ホストはリトルエンディアンの可能性がある。sockaddr フィールドを埋める際や回線から数値プロトコルフィールドを読む際は、htons/htonl/ntohs/ntohl で変換する。

1
2
// uint16_t port = htons(8080); host -> network
// uint16_t hp = ntohs(port); network -> host

Windows(Winsock)の違い:

Windows では winsock2.h を使い、ソケット使用前に WSAStartup を一度呼び、ソケットを閉じるには close() ではなく closesocket() を使う。それ以外の API は POSIX ソケットと一致しており、コードはほぼ移植可能。

1
2
3
// #include <winsock2.h> + ws2_32.lib
// WSAStartup(MAKEWORD(2,2), &wsaData); ... WSACleanup();
// close() -> closesocket()

エラーコード

ソケット呼び出しが失敗すると -1 を返し errno をセットする(ECONNREFUSED、ETIMEDOUT、ECONNRESET)。getaddrinfo は独自のエラーコードを返し gai_strerror で取得できる。各ステップで必ず確認すること。ここの無音失敗が後のハングに変わる。

1
2
3
// socket() -> -1 + errno
// connect() -> -1 + errno (ECONNREFUSED, ETIMEDOUT, EHOSTUNREACH)
// getaddrinfo() -> gai_strerror(rc)

HTTPS には TLS ライブラリが必要

生のソケットは TCP のみを提供する。暗号化には TLS ライブラリが必要——OpenSSL の場合はまず SSL_CTX を作り、SSL_new/SSL_connect でソケットを包み、SSL_read/SSL_write を使う——あるいは libcurl で高レベル HTTP+TLS API を使う。

1
// Raw sockets only give you TCP. See libcurl for a higher-level API.

16.時間と日付

壁時計時間、UTC/ローカル変換、strftime フォーマット、高解像度のモノトニックタイミング。

Unix エポック秒

time(NULL) は 1970-01-01 00:00 UTC からの整秒数(time_t)を返す——タイムスタンプ保存用のタイムゾーンを持たない普遍的な瞬間。

1
2
3
4
#include <time.h>
#include <stdio.h>
time_t now = time(NULL); // seconds since 1970-01-01 UTC
printf("%lld\n", (long long)now);

UTC / ローカル struct tm への変換

gmtime と localtime は time_t を struct tm(年、月、日、時……)に分解する——それぞれ UTC とローカルタイムゾーン。オフセットに注意:tm_year は 1900 からの年数、tm_mon は 0-11。

1
2
3
4
5
struct tm *utc = gmtime(&now); // UTC
struct tm *loc = localtime(&now); // local timezone
int year = utc->tm_year + 1900; // tm_year is years since 1900
int mon = utc->tm_mon + 1; // tm_mon is 0-11
int day = utc->tm_mday;

strftime でフォーマット

strftime は %Y %m %d %H:%M:%S 等で struct tm を可読テキストにフォーマットする——日付界の printf。ログ行や人間可読タイムスタンプを組み立てる標準的な方法。

1
2
3
4
5
char out[64];
strftime(out, sizeof out, "%Y-%m-%d %H:%M:%S", loc);
printf("%s\n", out); // 2026-08-02 14:05:09
// %Y year %m month %d day %H hour %M minute %S second
// %a weekday %A full name %z timezone offset %s epoch

日付を time_t に解析

strptime(POSIX)はテキストを struct tm に解析し、mktime で time_t に変換する。tm_isdst を -1 に設定すれば、mktime に夏時間を処理させ推測ミスを防げる。

1
2
3
4
struct tm t = {0};
strptime("2026-08-02", "%Y-%m-%d", &t); // POSIX
// t.tm_isdst = -1; // let mktime resolve DST
// time_t epoch = mktime(&t);

経過した壁時計時間

difftime(b, a) は 2 つの time_t 間の壁時計秒数を返す。シンプルで粗粒度の計測には十分だが、壁時計は飛ぶ——計測には単調クロックを使う。

1
2
3
4
time_t a = time(NULL);
/* work */
time_t b = time(NULL);
printf("%.0f s\n", difftime(b, a)); // difftime returns double

高精度単調クロック

clock_gettime(CLOCK_MONOTONIC) で計測する経過時間は NTP や夏時間調整の影響を受けない——ベンチマーク、タイムアウト、飛んでほしくないあらゆる計時に使う。

1
2
3
4
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts); // immune to wall-clock changes
long ms = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
// CLOCK_MONOTONIC: elapsed time. CLOCK_REALTIME: wall clock.

ナノ秒スリープ(C11 <time.h>)

nanosleep は timespec 精度(ナノ秒)でスレッドを停止させる。シグナルで割り込まれた場合は残り時間を報告するので、呼び出しは残りのスリープを続行できる。

1
2
struct timespec req = { .tv_sec = 0, .tv_nsec = 5000000 }; // 5 ms
nanosleep(&req, NULL);

本プロセスが消費した CPU 時間

clock() は本プロセスが消費した CPU 時間を返し、壁時計時間ではない。CPU 時間と壁時計時間を比較すれば、タスクが計算中か I/O 待機中かが見分けられる。

1
2
3
clock_t c = clock(); // CPU clock ticks
/* work */
printf("%.2f s\n", (double)(clock() - c) / CLOCKS_PER_SEC);

タイムゾーンオフセットの取得

tm_gmtoff(GNU/glibc)はローカル時間と UTC の秒単位オフセット(東を正)を保持し、推測なしに RFC 822 形式の +0800 オフセットを描画できる。

1
2
struct tm *l = localtime(&now);
printf("%ld\n", l->tm_gmtoff); // seconds east of UTC (GNU)

strftime なしで ISO 8601 を出力

snprintf で UTC struct tm のフィールドを連結して ISO 8601 タイムスタンプを作る。出力はロケール非依存で曖昧さがない——API とログに最適。

1
2
3
4
char iso[32];
snprintf(iso, sizeof iso, "%04d-%02d-%02dT%02d:%02d:%02dZ",
utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
utc->tm_hour, utc->tm_min, utc->tm_sec);

17.プロセスとシグナル

fork、exec、wait、プロセス間パイプ、シグナル処理、最小限のデーモン。

プラットフォームとヘッダファイル

fork/exec/wait とシグナルは POSIX(Linux/macOS);Windows は CreateProcess を使いモデルが異なる。本節の呼び出しには <unistd.h>、<sys/wait.h>、<signal.h> のインクルードが必要。

1
2
3
4
5
// POSIX only (Linux/macOS). Build: $ gcc prog.c -o prog
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>

fork——現在のプロセスを複製

fork は現在のプロセスを複製する。2 つのコピーは同じ復帰地点から続行する:子プロセスは pid 0 を見、親プロセスは子プロセスの pid を見る。子プロセスは _exit() すべき——main から return すると親プロセスのクリーンアップが走りバッファが再フラッシュされる。

1
2
3
4
5
6
7
8
9
10
11
12
pid_t pid = fork();
if (pid < 0) { perror("fork"); return 1; }
if (pid == 0) {
// child — this code runs only in the child
printf("child: pid=%d\n", getpid());
_exit(0); // child should _exit, not return
} else {
// parent
int status;
waitpid(pid, &status, 0); // block until child exits
if (WIFEXITED(status)) printf("exit=%d\n", WEXITSTATUS(status));
}

exec——プロセスイメージの置換

exec は現在のプロセスイメージを別のプログラムで置き換える:pid は変わらず、コードは全新。失敗時のみ復帰する(その後 _exit(127) を呼ぶ、shell の慣例)。環境、ファイルディスクリプタ、作業ディレクトリは保持される。

1
2
3
4
5
6
if (pid == 0) {
char *argv[] = { "ls", "-l", NULL };
execvp(argv[0], argv); // search PATH
perror("execvp");
_exit(127); // only reached on failure
}

fork/exec/wait でコマンド実行

外部コマンドの実行は shell 自身と同じ仕組み:fork(複製)、execvp(イメージ置換)、waitpid(回収)。WEXITSTATUS で子プロセスの終了コードを取り出し、WIFSIGNALED でシグナルで殺されたかを判定する。

1
2
3
4
5
6
7
int run(const char *cmd, char *const args[]) {
pid_t pid = fork();
if (pid == 0) { execvp(cmd, args); _exit(127); }
int status;
waitpid(pid, &status, 0);
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}

パイプ——子プロセスが書き、親プロセスが読む

pipe(fd) は連結されたディスクリプタ対を作る:片方が書き込み、片方が読み込み。fork 後に各プロセスは使わない側の端を閉じ、読み込み側は書き込み側が閉じると EOF を見る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fd[2];
pipe(fd);
pid_t p = fork();
if (p == 0) {
close(fd[0]); // child: close read end
write(fd[1], "hello\n", 6);
close(fd[1]);
_exit(0);
}
close(fd[1]); // parent: close write end
char buf[128];
ssize_t n = read(fd[0], buf, sizeof buf - 1);
buf[n] = '\0';
close(fd[0]);
waitpid(p, NULL, 0);

シグナルハンドラ

signal() は非同期シグナル用ハンドラを登録する。ハンドラ内では async-signal-safe な関数(write、_exit、signal、kill)のみ呼び出せる——printf や malloc は絶対不可、それらは再入可能でない。

1
2
3
4
5
6
7
void on_sigint(int sig) {
write(2, "interrupted\n", 12); // async-signal-safe only
_exit(130);
}
signal(SIGINT, on_sigint);
// Safe functions in a handler: write, _exit, signal, kill
// NOT safe: printf, malloc, most libc — use sigaction + sigqueue for real code.

sigaction——堅牢な API

sigaction は signal() の堅牢な代替:ハンドラ実行中に他のシグナルをマスクでき、SA_RESTART を設定して割り込まれたシステムコールを再開でき、ハンドラにどのシグナルが発火したかを伝えられる。新規コードはまずこれを使う。

1
2
3
4
5
struct sigaction sa = {0};
sa.sa_handler = on_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGTERM, &sa, NULL);

シグナルを一時的にブロック

sigprocmask はクリティカルセクションの前後でシグナルを一時的に延期し、後で解除する。これにより共有状態の更新途中でハンドラが割り込まなくなる。「保留されたイベント」を処理するハンドラと組み合わせれば、シグナル利用は安全になる。

1
2
3
4
5
6
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL);
/* critical section */
sigprocmask(SIG_UNBLOCK, &set, NULL);

プロセス終了 / 存在確認

kill(pid, sig) はプロセスにシグナルを送る;kill(pid, 0) はプロセス存在と送信権限のみを確認し、実際には送らない。負の pid はプロセスグループ全体を対象にする。

1
2
kill(pid, SIGTERM); // send signal to pid
// kill(pid, 0) == 0 -> process exists (may still be zombie)

ゾンビプロセス——子プロセスの回収

親プロセスより先に wait せずに終了した子プロセスはゾンビとなり、wait/waitpid で回収されるまで pid を占拠し続ける。SIGCHLD を無視すれば子プロセスは自動回収され、あるいはハンドラで明示的に回収する。

1
2
3
4
// Child that exits before parent waits becomes a zombie (shows as Z in ps).
// Reap via wait/waitpid, or install a SIGCHLD handler:
signal(SIGCHLD, SIG_IGN); // simple: auto-reap
// Or in the handler: while (waitpid(-1, NULL, WNOHANG) > 0) {}

デーモンプロセスの骨格(二重 fork)

デーモンは制御端末から離脱する:まず fork して子プロセスをプロセスグループリーダーにせず、次に setsid で新セッションを開始し、もう一度 fork して chdir('/') し、stdio を /dev/null またはログにリダイレクトする。

1
2
3
4
5
// if (fork() != 0) _exit(0); // 1st fork
// setsid(); // new session, detach tty
// if (fork() != 0) _exit(0); // 2nd fork — never session leader
// chdir("/"); umask(0);
// redirect stdin/out/err to /dev/null or a log file

18.POSIX 正規表現

regcomp / regexec / regerror:パターンのコンパイル、マッチとキャプチャ抽出、全マッチの反復。

セットアップとヘッダファイル

POSIX 正規表現(regcomp/regexec)は Linux/macOS の libc に組み込まれており、追加ライブラリは不要。REG_EXTENDED を渡すと ERE 構文を使う——デフォルトの BRE は別のもっと古い方言。

1
2
3
// POSIX regex (<regex.h>), Linux/macOS.
#include <regex.h>
#include <stdio.h>

コンパイルとマッチング

まず regcomp でパターンを一度コンパイルし(戻り値をチェック!)、次に regexec で文字列にマッチさせる。regfree でコンパイル済みパターンを解放する——regcomp ごとに regfree を一対一で対応させる。

1
2
3
4
5
6
7
8
9
10
11
12
regex_t re;
int rc = regcomp(&re, "^[a-z]+[0-9]+$", REG_EXTENDED);
if (rc != 0) { // REG_EXTENDED = modern ERE syntax
char err[128];
regerror(rc, &re, err, sizeof err);
fprintf(stderr, "regex: %s\n", err);
return 1;
}
rc = regexec(&re, "abc123", 0, NULL, 0);
if (rc == 0) puts("matched");
else if (rc == REG_NOMATCH) puts("no match");
regfree(&re); // always release

キャプチャグループ

括弧によるグループ化は regmatch_t エントリに格納される:m[1] は第 1 グループのマッチテキスト内の開始・終了オフセットを保持する。%.*s の精度トリックをオフセットと組み合わせて、キャプチャ部分を印刷する。

1
2
3
4
5
6
7
8
9
10
regmatch_t m[3]; // m[0]=whole, m[1]=group1, m[2]=group2
regex_t r2;
regcomp(&r2, "(\\w+)@(\\w+)\\.(\\w+)", REG_EXTENDED);
const char *text = "mail [email protected] now";
if (regexec(&r2, text, 3, m, 0) == 0) {
// m[i].rm_so/rm_eo are offsets into 'text'
printf("user: %.*s\n",
(int)(m[1].rm_eo - m[1].rm_so), text + m[1].rm_so);
}
regfree(&r2);

全マッチの反復(REG_NOTBOL ループ)

すべてのマッチを見つけるには、文字列末尾まで regexec をループし、各マッチを飛び越える。最初の反復後は REG_NOTBOL を渡して、^ が文字列途中で再アンカーされるのを防ぐ。

1
2
3
4
5
6
7
8
9
10
regex_t r3;
regcomp(&r3, "\\d+", REG_EXTENDED);
const char *s = "a1 b22 c333";
regmatch_t match;
int off = 0;
while (regexec(&r3, s + off, 1, &match, off ? REG_NOTBOL : 0) == 0) {
printf("num: %.*s\n", (int)(match.rm_eo - match.rm_so), s + off + match.rm_so);
off += match.rm_eo; // advance past the match
}
regfree(&r3);

よく使うフラグ

REG_EXTENDED は ERE 構文を有効にする(これを使う);REG_ICASE は大小文字無視マッチ;REG_NOSUB はキャプチャ追跡をスキップして高速化;REG_NEWLINE は ^/$ を行頭/行末にマッチさせ文字列全体にしない。

1
2
3
4
5
// REG_EXTENDED: ERE syntax (+, ?, |, (), {m,n})
// REG_ICASE: case-insensitive matching
// REG_NOSUB: skip capture info, faster
// REG_NEWLINE: '.' won't cross newlines; ^/$ anchor per line
// regexec(..., REG_NOTBOL): next char is not a line start

ERE 構文クイックリファレンス

ERE クイックリファレンス:. は任意の文字、^ $ はアンカー、[] は文字クラス、* + ? は繰り返し、{m,n} は範囲指定繰り返し、(a|b) は選択、後方参照、括弧内の POSIX クラス([[:alpha:]] など)。

1
2
3
4
// . any char ^ $ anchors [abc] [a-z] [^0-9] classes
// * + ? {2,4} repetition
// (a|b) groups \\1 backref \\d \\w \\s (GNU)
// POSIX classes: [[:alpha:]] [[:digit:]] [[:space:]] [[:alnum:]]

置換——組み込みなし;手動で出力を組み立てる

POSIX 正規表現には組み込み置換機能がない。出力を組み立てる:マッチをループし、マッチ間のテキストをコピーし、各マッチに置換文字列を挿入する——全マッチ反復ループと同じ要領。

1
2
3
// Step 1: regexec to find each match
// Step 2: copy text up to match, append replacement, continue
// Step 3: copy the tail. See regexec iteration in §3.

ユーザー入力のエスケープ

ユーザー入力をパターンに埋め込むには、まず各メタ文字([\\^$.|?*+()[])にバックスラッシュでエスケープを付ける。そうしないと巧妙に細工された入力がパターンの意味を変えてしまう——これはロジック問題だけでなくインジェクション脆弱性。

1
2
// Prefix every char in [\\^$.|?*+()[]{} with '\\' before regcomp
// to treat the input literally.

regcomp と regfree の確認

regcomp は不正なパターンで失敗する——必ず戻り値を確認し regerror でフォーマットすること。そして regcomp ごとに regfree を一対一で対応させないと、ループ内で繰り返しマッチするとコンパイル済みパターンがリークする。

1
2
// regcomp can fail (bad pattern) — see §1 error path.
// Every regcomp must pair with a regfree.

限界と性能

コンパイル済み regex_t を再利用する:マッチごとにコンパイルするのは無駄。POSIX 正規表現はバックトラックするため、病的パターンは信頼できない入力に対して遅くなり得る——重度または敵対的な用途には PCRE2 か RE2 を優先する。

1
2
3
// regcomp/regexec are compiled once; reuse the regex_t.
// Patterns are backtracking-based; pathological input can be slow
// (quadratic). For heavy use prefer PCRE2 or RE2.

コンパイル時マッチで switch 風ディスパッチ

起動時にいくつかのパターンを事前コンパイルし、順にチェーンさせて regexec を呼ぶ。入力が複数の形状にマッチし得る場合、これは明確なルーティングテーブルのように読める——文字列比較の壁よりずっと良い。

1
2
3
// if (regexec(&re_ipv4, s, 0, NULL, 0) == 0) {}
// else if (regexec(&re_ipv6, s, 0, NULL, 0) == 0) {}
// Pre-compile all patterns at startup, reuse them.

19.ビルドとデバッグ

コンパイラフラグ、最小限の Makefile、gdb、サニタイザ、valgrind —— FAQ セクションが警告するバグを見つけるツール。

よく使う gcc/clang フラグ

-std=c17 で言語を固定、-Wall -Wextra で有用な警告を有効化(常時オン)、-g でデバッグ情報を追加、-O0/-O2 で最適化レベルを選択、-fsanitize=address,undefined でランタイムチェックを追加。コストゼロで本物のバグも捕捉できる。

1
2
3
4
5
6
7
8
9
10
// -std=c17: language standard
// -Wall -Wextra: enable warnings (always)
// -Werror: warnings become errors (CI)
// -pedantic: reject non-ISO extensions
// -g: debug info (needed for gdb / asan traces)
// -O0 / -O1 / -O2: optimization levels
// -fsanitize=address,undefined: runtime checkers
// -o out: output name -I include dir
// -L libdir -lm: link libm -pthread
$ gcc -std=c17 -Wall -Wextra -g -O0 prog.c -o prog

Makefile——ミニマルなビルドドライバ

ミニマルな Makefile はコンパイラ、フラグ、ターゲット名、オブジェクト一覧を変数に入れ、暗黙ルール %.o:%.c に頼り、さらに clean ターゲットを提供する。make は変更部分のみ再ビルドする——まさにそれこそが意味。

1
2
3
4
5
6
7
8
9
10
11
// CC = gcc
// CFLAGS = -std=c17 -Wall -Wextra -g
// TARGET = app
// OBJS = main.o utils.o
// $(TARGET): $(OBJS)
// \t$(CC) $(CFLAGS) -o $@ $(OBJS)
// %.o: %.c
// \t$(CC) $(CFLAGS) -c $<
// clean:
// \trm -f $(TARGET) $(OBJS)
// $ make && ./app

デバッグビルド + リリースビルド

デバッグビルドは -g -O0 で完全シンボルを保持し最適化なし;リリースビルドは -O2 -DNDEBUG で assert もコンパイルアウトする。両方のターゲットを残し、ユーザーが実行した正確な構成でクラッシュを再現できるようにする。

1
2
3
// Debug: gcc -g -O0 -Wall -Wextra
// Release: gcc -O2 -DNDEBUG (asserts compiled out)
// Profile: gcc -O2 -pg + gprof ./app

gdb 基礎

gdb ./prog、そして:break main、run、next/step で逐行、print x で確認、backtrace でコールスタック、continue、quit。-batch -ex と組み合わせれば CI 用にクラッシュダンプを非対話で生成できる。

1
2
3
4
5
6
7
8
9
10
11
// $ gcc -g prog.c -o prog
// $ gdb ./prog
// (gdb) break main set breakpoint
// (gdb) run start
// (gdb) next / step line / into
// (gdb) print x inspect variable
// (gdb) backtrace call stack (bt)
// (gdb) list show source
// (gdb) continue resume (c)
// (gdb) quit
// Non-interactive: $ gdb -batch -ex 'run' -ex 'bt' ./prog

AddressSanitizer(ASan)

コンパイル時に -fsanitize=address,undefined を付ければ、プログラムは実行時にスタックバックトレース付きでオーバーフロー、Use-After-Free、リークを正確に報告する。valgrind より速く明瞭——真っ先に入れるべきツール。

1
2
3
// $ gcc -fsanitize=address,undefined -g prog.c -o prog
// $ ./prog -> detailed report on crash (use-after-free, overflow)
// Leak detection: $ ASAN_OPTIONS=detect_leaks=1 ./prog

valgrind メモリ検出

valgrind --leak-check=full ./prog は再コンパイル不要で不正な読み書き、Use-After-Free、リークを報告する——ただし約 20〜50 倍遅い。再コンパイル不可時や詳細が必要な時に使う。

1
2
3
4
// $ gcc -g prog.c -o prog
// $ valgrind --leak-check=full ./prog
// Reports: invalid reads/writes, use-after-free, definite leaks.
// Slower (~20-50x) — use on failure, not in production.

UBSan フラグ

-fsanitize=undefined は実行時に正確なレポートで符号付きオーバーフロー、未整列アクセスなどの未定義動作を捕捉する。-fsanitize=address と組み合わせれば、1 回のビルドでメモリと UB を同時にカバーできる。

1
2
3
// -fsanitize=undefined detects: signed overflow, shift UB,
// misaligned access, null deref, etc.
// Combine: -fsanitize=address,undefined

アセンブリ / プリプロセスへのコンパイル

gcc -S はアセンブリを出力、gcc -E はプリプロセス後のソースを表示、gcc -c はリンクしないオブジェクトファイルを生成する。それぞれがコンパイラとプリプロセッサが何をしたかを見る小さな窓。

1
2
3
// $ gcc -S prog.c -> prog.s (assembly)
// $ gcc -E prog.c -> preprocessed source
// $ gcc -O2 -c prog.c -> prog.o (object, no link)

静的解析(任意)

gcc -fanalyzer、clang --analyze、cppcheck はプログラム実行なしでバグを見つける——NULL ポインタ参照、リーク、未初期化使用。CI に組み込むのは安価で、テストが見逃すエラー種別も捕捉できる。

1
2
3
// $ gcc -fanalyzer prog.c GCC analyzer (GCC 10+)
// $ clang --analyze prog.c Clang static analyzer
// cppcheck prog.c third-party

ビルドシステム

Make は中小規模プロジェクトに適する;CMake は Make または Ninja ファイルを生成しクロスプラットフォームで移植可能;Meson はさらに高速。単一ファイルなら、Makefile 1 つまたは shell エイリアスで十分。

1
2
3
4
// Make (above) — small/medium projects
// CMake — portable, generates Make/Ninja files
// Meson — faster, more modern; Meson + Ninja
// For single files, just a Makefile or even a one-line shell alias.

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは ISO C(C11/C17)の自己完結型クイックリファレンスです。言語本体と、システムプログラミングの日常の約80%を処理する標準ライブラリ、さらに実プロジェクトで使う並行処理、ソケット、正規表現、ビルドツールをカバーします。正確なリファレンスは C11 標準(ISO/IEC 9899:2011)と cppreference の C セクションを参照してください。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てています —— 最初のプログラム、ポインタ、文字列、メモリ所有権、よくある落とし穴まで。各セクションは 6~10 のサブトピックに分かれ、各 5~15 行の簡潔な例が付いています。コードブロックは意図的に短く自己説明的で、各ブロックにコピーボタンがあり、そのままコンパイラに貼り付けられます。 すべてブラウザ内で実行されます —— アップロードもトラッキングもありません。このページは GuruToolkit の無料開発者ツールコレクションの一部です。ここにあるスニペットは自由に使用できますが、保証はありません。

バージョン 2.3.0