C チートシート — クイックリファレンス
C(C11/C17)の構文、ポインタ、メモリ、文字列、構造体、ファイル I/O を網羅する自己完結型の簡潔なリファレンス。さらにスレッド、ソケット、正規表現、時間、プロセス、ビルド/デバッグの要点も収録。日常の約80%のニーズをカバーします。
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 が何であるかを知ることができる。
コンパイルと実行(gcc / clang)
C はインタープリタ型ではなくコンパイル型言語である:コンパイラはソースコードを実行ファイルへ翻訳し、それを実行する。常に警告(-Wall -Wextra)を有効化し、言語標準(-std=c17)を固定することで、可搬性の問題をコンパイル時に表面化させる。
ツールチェーンと IDE のリスト
コンパイラとエディタが必要である。一般的な組み合わせ:Linux では gcc、macOS では clang、Windows では MinGW-w64 または MSVC、いずれも VS Code や CLion から呼び出せる。まずバージョン確認コマンドを一度実行してツールチェーンが利用可能であることを確かめてから、別の問題を調べること。
コマンドライン引数
main はユーザーがコマンドラインで入力した内容を受け取れる:argc は引数の個数、argv[] は引数そのもの——argv[0] は常にプログラム名である。引数は文字列として解析し、数値が必要なら strtol/strtod で変換する。
複数ファイル
大規模なプログラムは複数の .c ファイルに分割し、共有の .h ヘッダファイルに宣言を置く。各 .c ファイルは個別にコンパイルされ、その後リンクされる;ヘッダファイルが宣言と定義の一致を保ち、署名を変更すれば、それを使うすべての箇所が同時に露出する。
終了コード
main が返す値はシェルへ伝わる:0(EXIT_SUCCESS)は成功、それ以外は失敗を示す。スクリプトや CI はこの規約に依存しているため、終了コードには意味を持たせる必要がある。Linux/macOS では $? で、Windows では %ERRORLEVEL% で確認できる。
環境変数
getenv() はプロセス環境から変数を読み出し、その値を返す、未設定なら NULL を返す。再コンパイルせずに設定を注入する標準的な手段である——ただし、読み出した値は信頼できない入力として扱うこと。
プリプロセッサの基礎
プリプロセッサはコンパイル前に動作する:#define はマクロを定義し、# と ## はトークンを連結し、#ifdef/#if は条件付きコンパイルを行う。これは純粋なテキスト処理で、型を持たない層である——多くの場面では本物の関数や const を優先すべきである。
2.変数と定数
宣言、型のサイズ、定幅整数、const の位置、記憶クラス、typedef、列挙型。
基本的な宣言
変数を宣言するには型を先に書き、名前をその後ろに書き、宣言位置で初期化する(C99+)。宣言時に初期化することで、後から初期化されていないゴミの値を読み出すのを避ける。
型のサイズ(64 ビットでの典型値)
C は最小サイズのみを保証し、int はプラットフォームによって 16 ビットにも 32 ビットにもなり得る。sizeof を使って対象プラットフォームの実サイズを出力し、正直に扱い、バイト数をハードコードしてはならない。
固定幅整数型(C99+、<stdint.h>)
メモリ配置を厳密に制御したい場合——ファイルフォーマット、ネットワークプロトコル、ハッシュ——には <stdint.h> の int32_t/uint64_t などを使う。これらはプラットフォームが実際に提供する場合にのみ存在し、正確な幅を持つ名前が好まれる理由はまさにそこにある。
符号なし / 符号あり
符号なし型は決して負にならず、2^N の剰余で桁あふれする;符号あり型は負になり得る。両者を同一の式で混在させると、符号ありのオペランドは符号なしへ昇格される——これは典型的な落とし穴である(faq 節を参照)。
const——読み取り専用(コンパイル時検査)
const は変数を読み取り専用とし、コンパイラはビルド時に意図しない書き込みを拒否する。変更してはならないものにはすべて const を付ける:意図を示すだけでなく、コンパイラの最適化にも役立つ。
const ポインタの位置(定番の混同ポイント)
ポインタ宣言における const の位置は、何を保護するかを決める:* の前は指し先の値を、* の後はポインタ自体を保護する。型を右から左へ読めば、この 2 行の違いが見えてくる。
記憶域クラス
記憶域クラスは変数がどこに存在し、どれだけの間生存するかを決める。auto は局所変数の既定である;ファイルスコープに static を付けると名前をファイル外から見えなくする;局所変数に static を付けると呼び出しをまたいで値を保つ;extern は別所で定義された名前を参照する。
typedef——型の別名
typedef は型に新しい名前を付ける。新しい型を作るのではなく単なる別名である——しかし別名があれば、より短く一貫した型を書け、struct や関数ポインタの冗長さを読みやすい一語にまとめられる。
enum——名前付き定数
enum は関連する整数定数の集まりに名前を付ける。値は 0 から始まり 1 ずつ増えるが、明示的に代入したり範囲を指定したりすることもできる。列挙はそれ自体がドキュメントの役割を果たし、switch にも利用できる。
指定初期化(C99+)
C99 ではメンバを名前で初期化する(.port = 8080)ことや、配列要素を添字で初期化すること([3] = 9)ができ、順序は任意である。指定されなかったメンバは自動的にゼロで初期化される——長い位置指定の初期化よりずっと明快である。
複合リテラル(C99+)
複合リテラルはキャストに似た構文で、一時的な struct/配列値をインラインで構築する。一度きりの値を関数に渡したいとき、名前付きの変数を先に宣言する必要がなく便利である。
volatile(メモリマップド I/O / シグナル)
volatile はコンパイラに対し、ある変数がプログラムの通常の流れ以外で変化し得ることを伝える——ハードウェアレジスタ、シグナルハンドラ、別のスレッドなど。これにより、コンパイラによるアクセス時のキャッシュや並べ替えが抑止される。
3.構造体・共用体・集約型
構造体のレイアウト、共用体、ビットフィールド、sizeof、_Static_assert、アライメント —— 実戦的な C データの構成要素。
struct——異種レコード
struct は異なる型ではあるが互いに関連する値を一つのレコードにまとめる。メンバは宣言順に格納され、間にはアラインメント用のパディングが挿入される;したがって sizeof(struct) はメンバのサイズの合計より大きくなることが多い。
struct へのポインタ——-> でメンバにアクセス
アロー演算子 p->member は (*p).member の略記であり、構造体ポインタを逆参照してメンバへアクセスする。構造体をポインタで渡すとサイズが節約でき(一語分)、関数が呼び出し側のレコードを書き換えることも可能になる。
無名 struct + typedef
タグのない struct に typedef を付けることができ、一つの宣言でクリーンな型名を得ることができる。これはよく使うレコード型に名前を付ける慣習的な方法であり、处处に struct と書く手間を省く。
union——重なる記憶領域(サイズ = 最大のメンバ)
union は複数のメンバを同一のメモリ領域に置き、サイズは最大のメンバに従う。最後に書き込んだメンバのみが有効である——他のメンバを読むのは未定義動作となるため、タグ用のフィールドを併設する必要がある。
ビットフィールド——フラグの詰め込み
ビットフィールドは複数の小さなフラグを整数のビットに詰め込み、多くのブール値を含むレコードのメモリを節約する。レイアウトはコンパイラが決定するため、ビットフィールドは単一の翻訳単位内に留め、ABI の境界をまたいではならない。
sizeof——オブジェクトの占有バイト数
sizeof は型やオブジェクトが占めるバイト数を与える。スコープ内にまだある配列に対しては sizeof a / sizeof a[0] で要素数が得られる——ただし配列がポインタに_decay した時点で、このテクニックは静かに機能しなくなる(faq 参照)。
_Static_assert(C11+)コンパイル時検査
_Static_assert(cond, msg) はコンパイル時のアサーションであり、cond が偽ならビルドが失敗する。これはサイズや ABI への前提が崩れたその瞬間に検証するのに適しており、ランタイムまで持ち越さない。
アラインメント——& _Alignof(C11+)
アラインメントはオブジェクトをメモリ上のどのアドレスに置けるかを決める;コンパイラは struct に隙間をパディングし、各メンバを自然なアラインメントに揃える。_Alignof はオブジェクトのアラインメント値を報告し、_Alignas は必要なときにそれを上げる(SIMD、キャッシュラインの共有)。
フレキシブル配列メンバ(C99+)——struct 末尾の配列
struct は不定長の配列(data[])で終端でき、長さは割り当て時に決まる:1 回の malloc で struct に加えて追加バイトを確保する。これはバッファサイズが実行時まで分からない C の慣用パターンである。
列挙を独立した型として扱う(C23)
C23 では列挙の基底型を固定できる(enum Status : unsigned char)、サイズはコンパイラが決めるものではない。それ以外の場合、列挙は int と互換であり、ほとんどのコードではそれで十分である。
4.ポインタと配列
アドレス取得、デリファレンス、ポインタ演算、配列の減衰、ポインタ vs 配列、const の正しさ。
アドレス取得(&)と逆参照(*)
& はオブジェクトのアドレスを取り出してポインタを得る;* は逆の操作で、ポインタを介して読み書きする。この 2 つを組み合わせることで、値を間接的に参照し、別のスコープからそれを書き換えられる。
NULL——空ポインタ
NULL は「何も指していない」ポインタであり、合法なアドレスとは区別される。これを逆参照するのは未定義動作であり、通常は即座にクラッシュする——したがって使う前に必ず NULL かどうかを判定すること。
ポインタ演算(sizeof(*p) でスケール)
ポインタに整数を加えるのは要素単位での前進であり、バイト単位ではない——p + 2 は 2 つの int を飛び越す。添字 p[i] は実は *(p + i) のことである。配列の末尾を越えるのは未定義動作である。
配列の_decay(式はポインタになる)
ほとんどの式において、配列名は先頭要素を指すポインタとなる。結果として、関数内では引数から配列の長さを復元できない——長さを配列と一緒に渡す必要がある。
ポインタ vs 配列
char の配列は自らがバイトを保有し、書き換え可能である;文字列リテラルを指す char* は通常、読み取り専用メモリを指す。リテラルを指すポインタを通じて書き込むのは未定義動作であり、多くの場合クラッシュする。
多段階ポインタ
ポインタへのポインタ(int**)は一段間接性が増える。これは文字列の配列、二次元構造、そして「関数がメモリを確保し、呼び出し側が解放する責任を負う」出力引数に使われる。
関数ポインタ
関数ポインタは関数のアドレスを保持し、間接的に呼び出すことができる。これはコールバック、Dispatch テーブル、oop 節で扱うオブジェクト指向パターンの基盤である。
ポインタの配列(argv など)
char* の配列は文字列のリストを保持する慣用的な入れ物であり、argv がその代表例である。各要素はそれぞれ NUL 終端された文字列を指し、配列自体はポインタの並びに過ぎない。
const 正確性
ポインタ引数を const として宣言する——たとえば const char* s——ことは、関数が指し先の内容を変更しないと約束することに等しい。呼び出し側は文字列リテラルや配列を安心して渡せ、コンパイラはこの約束を強制する。
配列へのポインタ vs ポインタの配列
int *a[5] は 5 つのポインタからなる配列である;int (*b)[5] は 5 つの int の配列を指すポインタである。括弧がすべてを決める。複雑な宣言は右から左へ読むか、cdecl で復号する。
5.制御フロー
if/else、三項演算子、switch、for、while、do-while、break/continue、goto、setjmp/longjmp。
if / else if / else
条件によって分岐する:最初の条件が真の分岐が実行され、そうでなければ else へ進む。else-if の連鎖は順に複数の候補を試し、最初に一致した時点で止まる。
三項演算子(式)
cond ? a : b は文ではなく式である:cond に応じて a か b を取る。簡潔な値の選択に適している;1 行に収まらない、あるいは読みにくいなら if/else に切り替える。
switch——break しないとフォールスルー
switch は整数値で振り分ける。break しない限り、実行は次の case へフォールスルーする——意図的にフォールスルーを利用するか(空 case の積み重ね)、各 case で break する。default は一致しない値を処理する。
for——init でカウンタを宣言(C99+)
for(init; cond; step) は標準的なカウントループである。init 部でカウンタを宣言する(C99+)ことでその変数をループ内に閉じ込め、外側の同名の変数との衝突を防げる。
while / do-while
while は各反復の前に条件を判定し、1 回も実行されないことがある。do-while は常に 1 回実行してから判定する——メニューや確認プロンプトのような「少なくとも 1 回は尋ねる」場面に適している。
break / continue
break は最も内側のループまたは switch を即座に抜ける;continue は次の反復へ飛ぶ。うまく使えばネストを平坦化し、フラグ変数を排除できる;使い方を誤ると制御フローを隠してしまうため、層を少なく保つように努める。
goto——深くネストした箇所から抜ける(控えめに、慎重に)
goto はある行のラベルへジャンプする。正当な用途は深くネストした箇所からの脱出と、エラー処理の一元化——すなわち goto-cleanup イディオムである。それ以外では、return やフラグ変数を用いる方がほぼ常に明快である。
setjmp / longjmp 非局所ジャンプ
setjmp は実行コンテキストを保存し、longjmp が任意の位置からそれを復元し、保存点まで一気に巻き戻る。その間あらゆるクリーンアップは飛ばされ——デストラクタは実行されない——goto や明示的な return に比べて、通常は見合わない。
ループのイディオム
覚えておく価値のあるループの形:カウント範囲に対するインデックスループ、配列へのポインタ走査、break を使った無限ループ。意図を最も直接的に表現できるものを選ぼう。
短絡評価(ショートサーキット)
&& と || は左から右へ評価され、結果が確定した時点で停止する。これが ptr && ptr->x が安全な理由である:第2オペランドは第1が真のときにのみ実行されるため、行内でデリファレンスをガードできる。
6.関数と可変長引数
値渡し vs ポインタ渡し、関数ポインタと typedef、可変長引数、再帰、_Generic。
値渡し — 呼び出し元の変数は不変
C のすべての引数は値渡しである。呼び出された関数が受け取るのはコピーであり、それを変更しても呼び出し元には影響しない。単純なスカラーや小さな構造体はこれで渡す。変更が必要、あるいは大きなオブジェクトが心配な場合はポインタ形式を使う。
ポインタ渡し(呼び出し元を変更する)
呼び出し元の変数を変更したければ、そのアドレスを渡す。呼び出された関数がポインタをデリファレンスして書き込めば、変更は呼び出し元から見える。scanf のような関数がまさにこの方法で出力を生成している。
const 引数 — 読み取り専用の契約
const char* s のような const 引数は読み取り専用の契約である:関数はその指す内容を変更しないと約束する。これにより呼び出し側は文字列リテラルを安全に渡せるようになり、API の意図も明確になる。
配列引数の decay(長さも渡す必要あり)
配列引数はポインタに decay し、長さは一緒に渡されない。長さは必ず明示的に渡すこと。さもないと関数はデータの終わりを判断できない。
関数ポインタ + typedef
関数ポインタのシグネチャに一度 typedef を付けておき、以降は別名を変数や引数の宣言に使う。これでディスパッチテーブルやコールバックが、括弧とアスタリスクの壁のように読まずに済む。
可変引数 — printf 風関数
... 付きのシグネチャを持つ関数は printf のように可変個の引数を受け取る。va_start / va_arg / va_end で読み出す。固定引数で後続の引数の数を明示しないと、呼び出しを安全に解釈できない。
可変引数の転送 — v* 系関数
可変引数を別の可変引数関数(たとえばログラッパから vfprintf)に転送するには、v* 系を使い va_list をそのまま渡す。va_arg で再読込みするとリストが破壊される。
再帰
再帰関数はより小さな同種の問題で自身を呼び出し、基底条件に達するまで続ける。呼び出しごとにスタックを消費するため、深度が大きいまたは制御不能な場合は反復を選ぶ。
_Generic(C11+) — コンパイル時型ディスパッチ
_Generic(C11)は制御式の型に応じてコンパイル時に結果式を選択する。関数オーバーロードを軽量に模倣し、型安全なマクロを書く手段である。
noreturn(C11+)と inline
noreturn は決して返らない関数(致命的なエラールーチンなど)に付け、コンパイラが到達不能コードを推論できるようにする。inline は要求であって保証ではない。コンパイラは依然として実際の呼び出しを生成することがある。
7.文字列と数値変換
NUL 終端文字列、安全な書き込み、strtol/strtok/sscanf 変換、printf フォーマット表。
基本 — NUL で終わる文字配列
C の文字列は NUL バイト('\0')で終わる文字配列である。strlen は終端文字まで数える。文字列を保持するバッファには必ずその分のスペースを確保する。さもないと書き込みがオーバーフローする。
コピー / 比較 / 連結(安全版と非安全版)
strcpy / strcat は境界をチェックしないため、ソース文字列が大きすぎると静かにオーバーフローする。境界付きで常に NUL 終端される書き込みには snprintf を優先する。比較は strcmp / strncmp、strncat は最大 n 文字をコピーし終端文字を付加する。
検索
strchr は文字の最初の出現を、strrchr は最後の出現を、strstr は部分文字列の最初の出現を探す。いずれも文字列内部を指すポインタを返し、見つからない場合は NULL を返す。
mem* — 生バイト(任意のメモリを扱え、NUL も含む)
mem* 関数(memcpy、memmove、memset、memcmp)は生バイトを扱い、埋め込まれた NUL にも対応する。memcpy と memmove の違いは重なりの許容で、重なる可能性がある場合は memmove を使う。
数値変換:strtol / strtod
数値の解析には strtol / strtod を使う。これらはエラーを報告し、解析が止まった位置を示す。atoi は不正入力に対して黙って 0 を返すため違法入力を隠す。正確さを求めるコードでは使うべきではない。
堅牢な解析 — 文字列全体を検証する
完全な解析では 3 つのことを検証する必要がある。strtol 後に errno がセットされていないこと、最低 1 桁が消費されたこと(end != s)、文字列全体を読み終えたこと(*end == '\0')。どれか一つでも省略すれば末尾のゴミが入り込む。
トークン化:strtok_r(再入可能)
strtok は文字列をその場で変更し、区切り文字ごとに '\0' を挿入する。_r 接尾辞付きの版は再入可能(スレッドセーフ)である。元の文字列もまだ必要なら、まずコピーを取ること。
sscanf — フォーマット文字列で解析(一致数を返す)
sscanf はフォーマット文字列で文字列を解析し、成功したフィールドの一致数を返す。この戻り値を確認すること。期待より少なければ、入力は想定していた形ではない。
printf フォーマット一覧
printf ファミリーは共通のフォーマット言語を共有する:%d int、%ld long、%zu size_t、%f double、%s 文字列、%p ポインタに幅や精度の修飾子を加えたもの。フォーマット指定子は実引数の型と厳密に一致させる必要がある。一致しない場合は未定義動作である。
安全に文字列を連結する
snprintf は最大 n バイトを書き、常に NUL で終端するため、安全に文字列を連結する手段である。増え続けるテキストに対しては長さを記録しながらバッファを realloc するか、小さな動的バッファヘルパーを使う。
8.配列・ソート・検索
配列の反復、比較関数付き qsort / bsearch、二次元配列、構造体の配列。
固定長配列 — 長さを指定して反復
C の配列は固定サイズの連続した要素の塊である。長さを使って反復する。本物の配列がスコープ内にある間は sizeof a / sizeof *a が有効だが、関数に渡した瞬間に大きさは失われる。
初期化パターン
配列は位置、指定したインデックス([2] = 9)、または全体ゼロクリア({0})で初期化できる。二次元配列は配列の配列であり、行優先で格納される。m[i][j] は各行内で連続している。
qsort — 比較関数でソート
qsort は比較関数でインプレースソートを行い、比較関数は <0、0、>0 のいずれかを返す。比較関数には要素へのポインタが渡されるため、void* 引数を要素型に戻してから安全に比較する(オーバーフロー回避)。
bsearch — ソート済み配列での二分探索
bsearch は二分探索を行うが、ソート済みの配列に対してのみ機能する。一致した要素へのポインタを返し、見つからない場合は NULL を返す。大きな配列ではソートしてから検索するほうが線形スキャンより高速。
文字列比較関数(char* の配列)
char* の配列をソートするにはポインタ値ではなく文字列を比較する必要がある。比較関数を char* const* にキャストし、デリファレンスして各文字列ポインタを取得してから strcmp を呼ぶ。
構造体の配列 + フィールドでソート
構造体の配列をフィールドでソートするには、2 つの構造体ポインタを受け取りそのフィールドを比較する比較関数(たとえば a->key と b->key の三方比較)を書く。qsort がレコード全体を並び替えてくれる。
二次元配列ポインタ引数(二次元形状を保つ)
二次元配列を関数に渡すには、引数型に列数を含めなければならない。int m[][COLS] のように。これでコンパイラは各行の開始位置を計算できる。第 1 次元(行数)は自由に変化できる。
動的配列パターン(mem の節も参照)
拡張可能なコレクションは malloc したブロックであり、満杯になったら realloc する。容量と長さは別々に保持し、伸長時に 2 倍にし、使い終わったら free する。高級言語での vector / list に相当するものを C で実現する方法である。
可変長配列(C99、オプション)
可変長配列は実行時の値でスタック上に確保される(int vla[n])。C11 ではこれはオプションであり、n が大きすぎるとスタックを破裂させる可能性がある。大きいもの、ポータビリティが必要なものでは malloc を優先する。
二次元配列 — 行優先
int m[ROWS][COLS] は連続した行優先のブロックである。m[i][j] は *(*(m + i) + j) に等しい。インデックスは各行の長さを必要とし、行の開始位置を計算するため、形状はコンパイル時に確定していなければならない。
9.動的メモリと所有権
malloc/calloc/realloc/free、スタック vs ヒープ、アライメント、所有権のルール、goto cleanup パターン。
確保と解放
malloc は未初期化のヒープメモリを返し、確保したブロックはすべて最終的に解放しなければならない。使用前に必ず NULL かどうかを確認する。解放後はポインタを NULL にセットし、その後の二重解放は安全な no-op とする。
calloc:count × size のゼロクリア
calloc(count, size) はメモリをゼロクリアし、2 つの引数を掛け合わせ、オーバーフローも検出する。初期化済みストレージが必要で要素数が分かっている場合はこちらを優先する。
realloc — サイズ変更(メモリブロックが移動する可能性あり)
realloc は確保サイズを変更し、ブロックを新しいアドレスへ移動させる可能性がある。戻り値を一時変数で受け取り確認する。失敗時には古いブロックが依然有効だが、ポインタを先に上書きするとリークする。
スタック vs ヒープ
スタックメモリは自動管理で高速、関数から戻ると回収されるが、小さく短命である。ヒープメモリは手動管理で大きく、関数を越えて生存できる。大きく長寿命なバッファはヒープに置くべきである。
所有権 — シンプルなルール一式
最小限の所有権ルールを守るだけでメモリ問題を抑えられる。確保した側が解放する。ポインタは 1 回だけ解放する。解放後は NULL をセットする。realloc 失敗時は古いポインタを保持する。規約こそが C における唯一のリソースマネージャである。
ヒープ上の文字列
ヒープ上の文字列には strlen(s) + 1 バイトが必要で、その +1 は NUL 終端文字の分である。malloc(strlen(s) + 1) してからコピーする。+1 を忘れると起爆待ちの 1 バイトオーバーフローになる。
内部ポインタを持つ struct
ヒープデータを所有する struct は解放すべき 2 つの確保を持つ。最初に内部バッファを、次に struct 自体を解放する。内部ポインタの所有者を決定して明示し、確保の逆順で解放する。
goto cleanup(単一出口)
C にはデストラクタがないため、goto-cleanup が C 版の RAII である。関数ごとに 1 つの出口と 1 つのクリーンアップブロックを持ち、すべてのパスがそこに到達する。各リソースはすべてのパスでちょうど 1 回解放される。
アラインメント / 過剰アラインメント(C11)
aligned_alloc は指定したアラインメント(SIMD、キャッシュラインの共有など)でメモリを確保でき、サイズはアラインメント値の倍数でなければならない。通常の malloc はプラットフォームのデフォルトアラインメントのみを保証する。
メモリリーク / オーバーフロー検出ツール(build の節を参照)
リーク、オーバーフロー、use-after-free はすべてプロセスが死ぬまで隠れる。valgrind または AddressSanitizer で機械的に検出する。どちらも build の節で扱っており、追加できる最も安価な安全網である。
10.オブジェクト風パターン
C は手続き型ですが、構造体と関数ポインタでクラス、継承、多態をエミュレートできます。
「クラス」 = struct + vtable
C でクラスを模倣する。struct にデータを入れ、関数ポインタテーブル(vtable)に振る舞いを入れる。メソッドは明示的な self / this ポインタを引数に取る通常の関数である。
埋め込みによる継承の実現
基底クラスの struct を最初のメンバーとして埋め込めば継承になる。派生 struct のアドレスは基底クラスのアドレスと等しいので、基底クラスを期待するあらゆる場所で派生値を使え、基底クラスのメソッドはそのまま機能する。
コンストラクタ / デストラクタのペア
確保と初期化を担う init 関数と、解放と後始末を担う free 関数をペアにする。これが C 版のコンストラクタ / デストラクタである。作成された各オブジェクトは必ず 1 回だけ破棄しなければならない。
opaque ポインタ — 実装を隠す(PIMPL)
ヘッダファイルでは struct の前方宣言のみとし、定義は .c ファイルにだけ置く。呼び出し側はポインタを保持できるが内部は見えない。これが PIMPL であり、実装を隠して内部の変更が呼び出し側を破壊しないようにする。
インターフェース = 関数ポインタで構成された struct
関数ポインタで満たされた struct がインターフェースである。それを満たすどんな実装でも差し込め、各関数はインスタンスのコンテキストとして void* self を引数に取る。これが C 形態のポリモーフィズムである。
型消去データ(void *)
void* は型を消し去り、1 つのコンテナに異種の値を入れられるようにする。型を安全に戻すには、通常、ポインタが実際に何を指しているかを記録するタグフィールドを追加する必要がある。
*this を返すことによるメソッドチェーンの実現
各 setter はオブジェクト自身(*this)を返し、呼び出しを連鎖できる。builder->setA(x)->setB(y) のように。builder 形式の設定を簡潔にするが、戻り値が無視される可能性がある点を代償とする。
シングルトン(遅延初期化、スレッドセーフではない)
遅延初期化シングルトンは最初の使用時にインスタンスをstaticポインタに割り当てる。このシンプルなバージョンはスレッドセーフではなく、二つのスレッドが最初の割り当てを競合する可能性があり、共有する場合はロックが必要。
参照カウント(シンプル版)
retain/release はオブジェクトに参照カウントを割り当て、ゼロになった時点で解放する。これは Objective-C の基礎であり、C のオブジェクトにとってはガベージコレクションより軽量な所有権モデル。
完全な OOP も可能(glib)
C には完全な OOP フレームワークが存在する——glib と GObject がその証拠——ただし実際の複雑さも伴う。最小限の vtable と不透明ポインタでほとんどの要件をカバーできる。重厚な武器は真に必要なプロジェクトに残す。
11.エラー処理
例外はありません:errno、out パラメータ付き戻り値、goto cleanup、エラー列挙型、アサーション。
呼び出し失敗後の errno
ライブラリ関数やシステム関数は失敗時にグローバル errno をセットし、呼び出し直後に読むかコピーしなければならない——後続の呼び出しが上書きする可能性がある。strerror(errno) はエラーコードを読めるメッセージに変換する。
リターンコード + 出力引数(一般的な C の慣用句)
C の主流なエラー慣用句:成功時に 0 を返し、失敗時に負のエラーコードを返し、結果は出力引数のポインタで書き出す。失敗時には結果ポインタは NULL/不変のままで、呼び出し元はこれで判定できる。
エラー列挙——明示的なエラー分類
関数が生成する可能性のあるエラーの種類——OK、INVALID_INPUT、NOMEM、IO——を列挙し、それを返す。名前付きエラーコードはドキュメントとして機能し、switch に適し、あらゆる失敗パターンを明確に考えることを促す。
assert——デバッグ用(NDEBUG で削除)
assert(cond) は cond が偽のときプログラムを中断し、開発期にバグを捕捉する。-DNDEBUG でコンパイルされなくなるため、ユーザーが依存する動作のガードには決して使ってはならない——真と仮定している不変条件のみを検証する。
perror——errno の説明を出力
perror はあなたのメッセージと現在の errno の説明を stderr に 1 行で出力する。これはシステムコール失敗の理由を報告する最も速い方法——errno の部分は自分で書式文字列を書く必要がない。
_Static_assert——コンパイル時
_Static_assert は実行時ではなくコンパイル時に前提を検証する。これを使ってサイズ、オフセット、ABI 制約を検証し、本番バイナリで静かに失敗することを防ぐ。
スレッドセーフな strerror_r(POSIX)
strerror はスレッドセーフではなく、内部バッファを上書きする。strerror_r(POSIX)は呼び出し側が提供したバッファに書き込むため、並行スレッドはそれぞれのメッセージを取得できる。
システムコールの戻り値を確認する
システムコールは失敗する——ファイル、ソケット、メモリのいずれも。戻り値を無視すると、軽微な失敗が後になってクラッシュやデータ破損に変わる。fread/fwrite は成功しても短いカウントを返す可能性があるため、必ず確認すること。
ferror/feof でエラーとファイル終端を区別する
読み込みループの終了時、ferror() と feof() が理由を教えてくれる——真の I/O エラーか、ファイル終端に到達したか。EOF はエラーではなく正常な状態——ferror で両者を区別する。
12.ファイル I/O
stdio ストリーム、行・ファイル全体の読み込み、バイナリ I/O、POSIX ディスクリプタ、mmap、ディレクトリ走査。
オープン / クローズ
fopen は FILE* ストリームを返し、失敗時は NULL を返す。fclose はフラッシュして解放する。モードは r/w/a に + を加えると読み書き両用、b を加えるとバイナリ。オープン失敗後にそのポインタを使い続けてはならない。
ファイル全体を読み込む(小ファイル)
小ファイルの手順:末尾に seek し、ftell でサイズを取得し、rewind し、malloc で size+1 を確保し、fread で全部読む。読み込まれたバイト数と要求が一致することを確認——呼び出しの間にファイルが変更されている可能性がある。
1 行ずつ読み込み(慣用句)
fgets は 1 行を固定バッファに安全に読み込み、改行文字またはバッファ上限で停止する。末尾の改行を取り除くには strcspn を使う——改行はバッファに残される。これはテキストを 1 行ずつ処理する慣用的な方法。
書き込み
fprintf/fputs/fputc はそれぞれ書式付き出力、文字列、文字出力を行う。エラーは書き込み時にも fclose 時にも発生するため、fclose の結果を確認し、見落とされたフラッシュの失敗を防ぐ。
先に文字列にフォーマットする(安全)
先に snprintf で出力全体をバッファに組み立て、一度に書き出す。これで並行ソースからの交错した書き込みを避け、書き込み前に検証でき、実際にも書き込みのアトミック性を維持できる。
バイナリ I/O——生バイトの読み書き
fread/fwrite は生バイトを扱い、固定長レコードに最適:ちょうど sizeof(record) を読み、戻り値を処理する。短い読み込みはエラーか EOF——ferror/feof で区別する。
POSIX ファイルディスクリプタ
stdio の下に、Linux/macOS は int ファイルディスクリプタ用の open/read/write/close を提供する:細かい制御が可能でバッファリングなし、落とし穴も多い。境界を越えるには fdopen(fd を FILE* に)または fileno(FILE* を fd に)を使う。
mmap——ファイルをメモリにマップする
mmap はファイルをアドレス空間に直接マップし、読み書きが通常のメモリアクセスになる——大ファイルで高速。munmap で解放する。ファイルは空ではならず、マップサイズはページ境界に揃える。
ディレクトリの走査
opendir/readdir/closedir はディレクトリ項目を一つずつ列挙する。暗黙の「.」と「..」エントリをスキップし、stat で各名前のファイルタイプとサイズを調べる。
stat——ファイルのメタデータ
stat/lstat/fstat はファイルのメタデータ——サイズ、更新時刻、パーミッション、タイプ——を返す。tmpfile() は匿名のテンポラリファイルを提供し、クローズ時に自動削除される。一時データの保存に適している。
13.よくある落とし穴
定番の C の罠 10 個 —— 正しい書き方には GOOD、間違った書き方には BAD の印が付きます。
バッファオーバーフロー
固定バッファの末尾を超えて書き込むと隣接メモリを破損させ、最高レベルのセキュリティ脆弱性となる。すべての書き込みに上限を設定:strncpy で手動の NUL 補完、snprintf、または長さ指定の fgets。サニタイザは人目では見逃す問題を捕捉できる。
解放後の使用 / 二重解放
解放後の使用は未定義動作であり、しばしば悪用可能。同じメモリを 2 回解放するとアロケータが破壊される。free 後すぐにポインタを NULL に設定する——free(NULL) は安全な何もしない操作。
メモリリーク
すべての割り当てをすべてのパスで解放しなければならない。リークは静かに膨らみ、プロセスが終了するまで続く。AddressSanitizer と valgrind はデフォルトで報告する——クラッシュした後ではなく、開発期に実行する。
= と ==
単一の = は代入;== は比較。if (x = 0) は 0 を代入し常に偽となる——静かで発見しにくいバグ。-Wall でコンパイルし、条件内の代入には括弧を付ける。
符号付きオーバーフローは UB
符号付き整数オーバーフローは未定義動作:コンパイラはそれが決して発生しないと仮定して最適化する可能性がある。大きな符号付き値を加算する前に long long に拡張するか、本来意図する符号なしラップアラウンドを使う。
未初期化変数
未初期化のローカル変数の読み取りは未定義動作で、通常はゴミの値を取得する。宣言時にすべての変数を初期化する——コンパイラの -Wuninitialized が初めて意味を持つ。
sizeof における配列の decay
関数内部では配列パラメータはポインタとなり、sizeof は 8(ポインタサイズ)を返し、配列サイズではない。sizeof a / sizeof a[0] は本物の配列がスコープ内にある場合のみ有効——明示的に長さを渡す。
off-by-one エラー
ループ境界は古典的な off-by-one エラー:n 個の要素を i < n(インデックス 0..n-1)で反復すべきで、i <= n のように 1 つ多く触れて境界外にはならない。サイズ n の配列の最後の有効なインデックスは n-1。
printf の書式不一致
書式指定子と引数の型の不一致は未定義動作——%s に int を渡すと直接クラッシュする可能性。size_t には %zu、%d/%ld/%lld は型を一致させ、-Wformat を有効にして不一致を捕捉する。
符号付き / 符号なし比較
符号付きと符号なしの比較では、符号付きオペランドは符号なしに昇格され、負の値は非常に大きな正の値になる。両側を明示的に同じ符号付き型にキャストするか、そもそも混在させない。
14.スレッドと並行処理
pthread create/join、mutex、条件変数、アトミック、C11 スレッド —— ビルドとリンクに -pthread を使用。
ビルドとヘッダファイル
スレッドプログラムは -pthread でコンパイルし、pthread ライブラリをリンクする。<pthread.h> をインクルードし、各 pthread_* の戻り値を確認するエラーとして扱う——これらの API は errno を設定しない。
作成と待機
pthread_create はある関数を実行するスレッドを起動し、その関数は void* 引数を受け取る。pthread_join はその終了をブロックして待機する。渡された引数と返される void* がスレッドを越えてデータを渡すチャネルとなる。
void* でデータを返す
スレッドは結果を void* で返す——通常は malloc された値へのポインタ。呼び出し側は join 後に返されたポインタをキャストし直して値を読み出し、解放する。
ミューテックス——共有状態の保護
ミューテックスは共有状態へのアクセスを直列化する:アクセス前にロックし、後にアンロックする。クリティカルセクションは短く保つ——ロックはボトルネックであり、長時間保持するとデッドロックを招く。
条件変数——待機 / 通知
条件変数は、別スレッドが述語の変化を通知するまでスレッドをスリープさせる。常に while ループ内で待機(偽の起床がある)し、ミューテックスを保持し、目覚めたら述語を再確認する。
デタッチスレッド——join 不要
デタッチスレッドは終了時に自らクリーンアップし、join 不要。決して待機しないと確定した場合のみデタッチする。また、呼び出し側スタックへのポインタをデタッチスレッドに渡してはならない——その時点でスタックは既に消えている可能性がある。
C11 スレッド(ポータブル)
C11 の <threads.h>(thrd_t)は pthreads より薄く、ポータブルな代替——機能は弱く、多くの場合 OS API の薄いラッパーにすぎない。POSIX で本格的なことを行うには pthreads が依然として標準の選択肢。
アトミック操作(C11)——<stdatomic.h>
atomic_int と atomic_fetch_add は、定義済みメモリ順序のロックフリーなロード、ストア、インクリメントを提供する——ミューテックスより速いシンプルな共有カウンターで、通常の int より正しい(競合しない)。
スレッドローカルストレージ(C11)
_Thread_local(C11、GCC では __thread)は各スレッドに変数の独自のコピーを持たせる。スレッドごとのキャッシュに適し、互いに上書きしてはならないエラースロットにも適する。
スレッドのエラー処理
pthread 関数は errno をセットするのではなく、errno スタイルのエラーコードを返す。戻り値を受け取り、strerror(rc) で診断メッセージをフォーマットする——確認されない失敗は通常は謎のハングアップやクラッシュとして現れる。
デッドロック回避
デッドロックは「必要なものを保持しながら別で必要なものを待つ」ことから生じる。固定されたグローバル順序でロックし、クリティカルセクションをできるかぎり小さくし、優先的に 1 リソース 1 ロックとし、有界待機が必要なときはタイムアウト付きロックを使う。
15.ネットワーク(ソケット)
getaddrinfo、TCP クライアントとサーバ、最小限の HTTP リクエスト —— Linux/macOS では追加ライブラリなしでリンク。
ビルドとヘッダファイル
POSIX ソケットには <sys/socket.h>、<netinet/in.h>、<arpa/inet.h> が必要。Linux/macOS では追加リンクライブラリは不要。アドレス解決には getaddrinfo を使い、sockaddr 構造体を手で埋めない。
TCP クライアント——解決、接続、送受信
TCP クライアントは getaddrinfo でサーバーを解決し、ソケットを作成し、connect する。アドレスリストは長い場合がある——順次試行し、最初に成功したものを使い、失敗したものはすぐに閉じる。
送信 / 受信
send/recv は接続済みソケット上でバイトをやりとりするが、一度に全部を運ぶ保証はない。メッセージ全体を受信するか EOF に達するまでループし、残りのバイト数を追跡する。
TCP サーバー——ソケットの流れ
サーバーはソケットを作成し、SO_REUSEADDR を設定し、ポートに bind し、listen し、accept のループに入る。accept ごとにそのクライアント専用の新しいソケットが返される——リスニングソケットは決して読み込まれない。
ホスト名を IP に解決
getaddrinfo はホスト名とポートを候補アドレスのリストに変換し、IPv4/IPv6 とサービス名も処理する。成功するまで順次試し、freeaddrinfo で解放する。
ブロッキング vs ノンブロッキング
ソケットのデフォルトはブロッキング:recv はデータが来るまで待機する。O_NONBLOCK を設定すると、準備ができていないときに EAGAIN を返す。poll/epoll/select を使うことで、シングルスレッドで多数のソケットを監視できる。
UDP——sendto/recvfrom(コネクションレス)
UDP はコネクションレス:sendto/recvfrom は毎回送信先アドレスを付ける必要があり、ハンドシェイクも順序も到達保証もない。遅延に敏感で損失を許容できるトラフィックに適している。
バイト順——htons/htonl とネットワークバイト順
ネットワークバイト順はビッグエンディアン、ホストはリトルエンディアンの可能性がある。sockaddr フィールドを埋める際や回線から数値プロトコルフィールドを読む際は、htons/htonl/ntohs/ntohl で変換する。
Windows(Winsock)の違い:
Windows では winsock2.h を使い、ソケット使用前に WSAStartup を一度呼び、ソケットを閉じるには close() ではなく closesocket() を使う。それ以外の API は POSIX ソケットと一致しており、コードはほぼ移植可能。
エラーコード
ソケット呼び出しが失敗すると -1 を返し errno をセットする(ECONNREFUSED、ETIMEDOUT、ECONNRESET)。getaddrinfo は独自のエラーコードを返し gai_strerror で取得できる。各ステップで必ず確認すること。ここの無音失敗が後のハングに変わる。
HTTPS には TLS ライブラリが必要
生のソケットは TCP のみを提供する。暗号化には TLS ライブラリが必要——OpenSSL の場合はまず SSL_CTX を作り、SSL_new/SSL_connect でソケットを包み、SSL_read/SSL_write を使う——あるいは libcurl で高レベル HTTP+TLS API を使う。
16.時間と日付
壁時計時間、UTC/ローカル変換、strftime フォーマット、高解像度のモノトニックタイミング。
Unix エポック秒
time(NULL) は 1970-01-01 00:00 UTC からの整秒数(time_t)を返す——タイムスタンプ保存用のタイムゾーンを持たない普遍的な瞬間。
UTC / ローカル struct tm への変換
gmtime と localtime は time_t を struct tm(年、月、日、時……)に分解する——それぞれ UTC とローカルタイムゾーン。オフセットに注意:tm_year は 1900 からの年数、tm_mon は 0-11。
strftime でフォーマット
strftime は %Y %m %d %H:%M:%S 等で struct tm を可読テキストにフォーマットする——日付界の printf。ログ行や人間可読タイムスタンプを組み立てる標準的な方法。
日付を time_t に解析
strptime(POSIX)はテキストを struct tm に解析し、mktime で time_t に変換する。tm_isdst を -1 に設定すれば、mktime に夏時間を処理させ推測ミスを防げる。
経過した壁時計時間
difftime(b, a) は 2 つの time_t 間の壁時計秒数を返す。シンプルで粗粒度の計測には十分だが、壁時計は飛ぶ——計測には単調クロックを使う。
高精度単調クロック
clock_gettime(CLOCK_MONOTONIC) で計測する経過時間は NTP や夏時間調整の影響を受けない——ベンチマーク、タイムアウト、飛んでほしくないあらゆる計時に使う。
ナノ秒スリープ(C11 <time.h>)
nanosleep は timespec 精度(ナノ秒)でスレッドを停止させる。シグナルで割り込まれた場合は残り時間を報告するので、呼び出しは残りのスリープを続行できる。
本プロセスが消費した CPU 時間
clock() は本プロセスが消費した CPU 時間を返し、壁時計時間ではない。CPU 時間と壁時計時間を比較すれば、タスクが計算中か I/O 待機中かが見分けられる。
タイムゾーンオフセットの取得
tm_gmtoff(GNU/glibc)はローカル時間と UTC の秒単位オフセット(東を正)を保持し、推測なしに RFC 822 形式の +0800 オフセットを描画できる。
strftime なしで ISO 8601 を出力
snprintf で UTC struct tm のフィールドを連結して ISO 8601 タイムスタンプを作る。出力はロケール非依存で曖昧さがない——API とログに最適。
17.プロセスとシグナル
fork、exec、wait、プロセス間パイプ、シグナル処理、最小限のデーモン。
プラットフォームとヘッダファイル
fork/exec/wait とシグナルは POSIX(Linux/macOS);Windows は CreateProcess を使いモデルが異なる。本節の呼び出しには <unistd.h>、<sys/wait.h>、<signal.h> のインクルードが必要。
fork——現在のプロセスを複製
fork は現在のプロセスを複製する。2 つのコピーは同じ復帰地点から続行する:子プロセスは pid 0 を見、親プロセスは子プロセスの pid を見る。子プロセスは _exit() すべき——main から return すると親プロセスのクリーンアップが走りバッファが再フラッシュされる。
exec——プロセスイメージの置換
exec は現在のプロセスイメージを別のプログラムで置き換える:pid は変わらず、コードは全新。失敗時のみ復帰する(その後 _exit(127) を呼ぶ、shell の慣例)。環境、ファイルディスクリプタ、作業ディレクトリは保持される。
fork/exec/wait でコマンド実行
外部コマンドの実行は shell 自身と同じ仕組み:fork(複製)、execvp(イメージ置換)、waitpid(回収)。WEXITSTATUS で子プロセスの終了コードを取り出し、WIFSIGNALED でシグナルで殺されたかを判定する。
パイプ——子プロセスが書き、親プロセスが読む
pipe(fd) は連結されたディスクリプタ対を作る:片方が書き込み、片方が読み込み。fork 後に各プロセスは使わない側の端を閉じ、読み込み側は書き込み側が閉じると EOF を見る。
シグナルハンドラ
signal() は非同期シグナル用ハンドラを登録する。ハンドラ内では async-signal-safe な関数(write、_exit、signal、kill)のみ呼び出せる——printf や malloc は絶対不可、それらは再入可能でない。
sigaction——堅牢な API
sigaction は signal() の堅牢な代替:ハンドラ実行中に他のシグナルをマスクでき、SA_RESTART を設定して割り込まれたシステムコールを再開でき、ハンドラにどのシグナルが発火したかを伝えられる。新規コードはまずこれを使う。
シグナルを一時的にブロック
sigprocmask はクリティカルセクションの前後でシグナルを一時的に延期し、後で解除する。これにより共有状態の更新途中でハンドラが割り込まなくなる。「保留されたイベント」を処理するハンドラと組み合わせれば、シグナル利用は安全になる。
プロセス終了 / 存在確認
kill(pid, sig) はプロセスにシグナルを送る;kill(pid, 0) はプロセス存在と送信権限のみを確認し、実際には送らない。負の pid はプロセスグループ全体を対象にする。
ゾンビプロセス——子プロセスの回収
親プロセスより先に wait せずに終了した子プロセスはゾンビとなり、wait/waitpid で回収されるまで pid を占拠し続ける。SIGCHLD を無視すれば子プロセスは自動回収され、あるいはハンドラで明示的に回収する。
デーモンプロセスの骨格(二重 fork)
デーモンは制御端末から離脱する:まず fork して子プロセスをプロセスグループリーダーにせず、次に setsid で新セッションを開始し、もう一度 fork して chdir('/') し、stdio を /dev/null またはログにリダイレクトする。
18.POSIX 正規表現
regcomp / regexec / regerror:パターンのコンパイル、マッチとキャプチャ抽出、全マッチの反復。
セットアップとヘッダファイル
POSIX 正規表現(regcomp/regexec)は Linux/macOS の libc に組み込まれており、追加ライブラリは不要。REG_EXTENDED を渡すと ERE 構文を使う——デフォルトの BRE は別のもっと古い方言。
コンパイルとマッチング
まず regcomp でパターンを一度コンパイルし(戻り値をチェック!)、次に regexec で文字列にマッチさせる。regfree でコンパイル済みパターンを解放する——regcomp ごとに regfree を一対一で対応させる。
キャプチャグループ
括弧によるグループ化は regmatch_t エントリに格納される:m[1] は第 1 グループのマッチテキスト内の開始・終了オフセットを保持する。%.*s の精度トリックをオフセットと組み合わせて、キャプチャ部分を印刷する。
全マッチの反復(REG_NOTBOL ループ)
すべてのマッチを見つけるには、文字列末尾まで regexec をループし、各マッチを飛び越える。最初の反復後は REG_NOTBOL を渡して、^ が文字列途中で再アンカーされるのを防ぐ。
よく使うフラグ
REG_EXTENDED は ERE 構文を有効にする(これを使う);REG_ICASE は大小文字無視マッチ;REG_NOSUB はキャプチャ追跡をスキップして高速化;REG_NEWLINE は ^/$ を行頭/行末にマッチさせ文字列全体にしない。
ERE 構文クイックリファレンス
ERE クイックリファレンス:. は任意の文字、^ $ はアンカー、[] は文字クラス、* + ? は繰り返し、{m,n} は範囲指定繰り返し、(a|b) は選択、後方参照、括弧内の POSIX クラス([[:alpha:]] など)。
置換——組み込みなし;手動で出力を組み立てる
POSIX 正規表現には組み込み置換機能がない。出力を組み立てる:マッチをループし、マッチ間のテキストをコピーし、各マッチに置換文字列を挿入する——全マッチ反復ループと同じ要領。
ユーザー入力のエスケープ
ユーザー入力をパターンに埋め込むには、まず各メタ文字([\\^$.|?*+()[])にバックスラッシュでエスケープを付ける。そうしないと巧妙に細工された入力がパターンの意味を変えてしまう——これはロジック問題だけでなくインジェクション脆弱性。
regcomp と regfree の確認
regcomp は不正なパターンで失敗する——必ず戻り値を確認し regerror でフォーマットすること。そして regcomp ごとに regfree を一対一で対応させないと、ループ内で繰り返しマッチするとコンパイル済みパターンがリークする。
限界と性能
コンパイル済み regex_t を再利用する:マッチごとにコンパイルするのは無駄。POSIX 正規表現はバックトラックするため、病的パターンは信頼できない入力に対して遅くなり得る——重度または敵対的な用途には PCRE2 か RE2 を優先する。
コンパイル時マッチで switch 風ディスパッチ
起動時にいくつかのパターンを事前コンパイルし、順にチェーンさせて regexec を呼ぶ。入力が複数の形状にマッチし得る場合、これは明確なルーティングテーブルのように読める——文字列比較の壁よりずっと良い。
19.ビルドとデバッグ
コンパイラフラグ、最小限の Makefile、gdb、サニタイザ、valgrind —— FAQ セクションが警告するバグを見つけるツール。
よく使う gcc/clang フラグ
-std=c17 で言語を固定、-Wall -Wextra で有用な警告を有効化(常時オン)、-g でデバッグ情報を追加、-O0/-O2 で最適化レベルを選択、-fsanitize=address,undefined でランタイムチェックを追加。コストゼロで本物のバグも捕捉できる。
Makefile——ミニマルなビルドドライバ
ミニマルな Makefile はコンパイラ、フラグ、ターゲット名、オブジェクト一覧を変数に入れ、暗黙ルール %.o:%.c に頼り、さらに clean ターゲットを提供する。make は変更部分のみ再ビルドする——まさにそれこそが意味。
デバッグビルド + リリースビルド
デバッグビルドは -g -O0 で完全シンボルを保持し最適化なし;リリースビルドは -O2 -DNDEBUG で assert もコンパイルアウトする。両方のターゲットを残し、ユーザーが実行した正確な構成でクラッシュを再現できるようにする。
gdb 基礎
gdb ./prog、そして:break main、run、next/step で逐行、print x で確認、backtrace でコールスタック、continue、quit。-batch -ex と組み合わせれば CI 用にクラッシュダンプを非対話で生成できる。
AddressSanitizer(ASan)
コンパイル時に -fsanitize=address,undefined を付ければ、プログラムは実行時にスタックバックトレース付きでオーバーフロー、Use-After-Free、リークを正確に報告する。valgrind より速く明瞭——真っ先に入れるべきツール。
valgrind メモリ検出
valgrind --leak-check=full ./prog は再コンパイル不要で不正な読み書き、Use-After-Free、リークを報告する——ただし約 20〜50 倍遅い。再コンパイル不可時や詳細が必要な時に使う。
UBSan フラグ
-fsanitize=undefined は実行時に正確なレポートで符号付きオーバーフロー、未整列アクセスなどの未定義動作を捕捉する。-fsanitize=address と組み合わせれば、1 回のビルドでメモリと UB を同時にカバーできる。
アセンブリ / プリプロセスへのコンパイル
gcc -S はアセンブリを出力、gcc -E はプリプロセス後のソースを表示、gcc -c はリンクしないオブジェクトファイルを生成する。それぞれがコンパイラとプリプロセッサが何をしたかを見る小さな窓。
静的解析(任意)
gcc -fanalyzer、clang --analyze、cppcheck はプログラム実行なしでバグを見つける——NULL ポインタ参照、リーク、未初期化使用。CI に組み込むのは安価で、テストが見逃すエラー種別も捕捉できる。
ビルドシステム
Make は中小規模プロジェクトに適する;CMake は Make または Ninja ファイルを生成しクロスプラットフォームで移植可能;Meson はさらに高速。単一ファイルなら、Makefile 1 つまたは shell エイリアスで十分。
公式リンク
公式ドキュメントとリソースへの直接リンク。
このチートシートについて
このページは ISO C(C11/C17)の自己完結型クイックリファレンスです。言語本体と、システムプログラミングの日常の約80%を処理する標準ライブラリ、さらに実プロジェクトで使う並行処理、ソケット、正規表現、ビルドツールをカバーします。正確なリファレンスは C11 標準(ISO/IEC 9899:2011)と cppreference の C セクションを参照してください。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てています —— 最初のプログラム、ポインタ、文字列、メモリ所有権、よくある落とし穴まで。各セクションは 6~10 のサブトピックに分かれ、各 5~15 行の簡潔な例が付いています。コードブロックは意図的に短く自己説明的で、各ブロックにコピーボタンがあり、そのままコンパイラに貼り付けられます。 すべてブラウザ内で実行されます —— アップロードもトラッキングもありません。このページは GuruToolkit の無料開発者ツールコレクションの一部です。ここにあるスニペットは自由に使用できますが、保証はありません。
バージョン 2.3.0