C: 慣用的効率リファレンス
目次
1. メモリ管理 {#memory}
// ❌ 戻り値をチェックせずにmalloc
char *buf = malloc(size);
strcpy(buf, src);
// ✅
char *buf = malloc(size);
if (!buf) return -ENOMEM;
memcpy(buf, src, size);
// ❌ mallocの結果をキャスト(Cでは不要。 #include 忘れを隠す)
int *p = (int *)malloc(n * sizeof(int));
// ✅
int *p = malloc(n * sizeof *p);
// ❌ freeしてもポインタをNULLにしない(長寿命スコープではダングリングポインタのリスク)
free(ptr);
// ... 後続のコードがptrを使うかもしれない
// ✅
free(ptr);
ptr = NULL;
// ❌ 早期リターンパスでfreeを忘れる
char *a = malloc(100);
char *b = malloc(200);
if (!b) return -1; // aがリーク
// ✅ — 単一のクリーンアップラベル
char *a = NULL, *b = NULL;
a = malloc(100);
if (!a) goto cleanup;
b = malloc(200);
if (!b) goto cleanup;
// ... a, b を使う ...
cleanup:
free(b);
free(a);
sizeof(Type) ではなく sizeof *ptr を使用する — 型が変更されても正しく動作します。
2. ポインタと配列 {#pointers}
// ❌ 手動で配列サイズを追跡
void process(int *arr, int len) { ... }
process(data, 10);
// ✅ — サイズをポインタと一緒に渡すか、構造体を使う
typedef struct { int *data; size_t len; } IntSlice;
// ❌ 配列インデックスのほうが明確なのにポインタ演算を使う
*(arr + i) = value;
// ✅
arr[i] = value;
// ❌ プロダクションコードでのVLA(スタックオーバーフローのリスク、C11以降はオプション)
int arr[n];
// ✅
int *arr = malloc(n * sizeof *arr);
if (!arr) return -ENOMEM;
// ... arr を使う ...
free(arr);
3. エラーハンドリング {#errors}
// ❌ エラーリターンにマジックナンバーを使う
if (do_thing() == -1) { ... }
// ✅ — 名前付きエラーコードを定義または使用する
#include <errno.h>
if (do_thing() < 0) {
perror("do_thing");
return errno;
}
// ❌ 深くネストしたエラーチェック
int r1 = step1();
if (r1 == 0) {
int r2 = step2();
if (r2 == 0) {
int r3 = step3();
// ...
}
}
// ✅ — 早期リターン / gotoクリーンアップ
if (step1() < 0) goto fail;
if (step2() < 0) goto fail;
if (step3() < 0) goto fail;
return 0;
fail:
cleanup();
return -1;
goto cleanup はリソース解放のための一般的なC言語のイディオムです — 原則として避けようとしないでください。
4. 文字列 {#strings}
// ❌ 境界チェックなしのstrcpy
strcpy(dest, src);
// ✅
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// またはより良い方法: snprintf(dest, sizeof(dest), "%s", src);
// ❌ strcmpの誤用
if (str == "hello") { ... } // ポインタを比較、内容ではない
// ✅
if (strcmp(str, "hello") == 0) { ... }
// ❌ 繰り返しstrcatで文字列を構築(O(n²))
char result[1024] = "";
for (int i = 0; i < n; i++) {
strcat(result, items[i]);
}
// ✅ — 書き込み位置を追跡
char result[1024];
int pos = 0;
for (int i = 0; i < n && pos < (int)sizeof(result); i++) {
pos += snprintf(result + pos, sizeof(result) - pos, "%s", items[i]);
}
sprintf よりも snprintf を常に優先すること。
5. 構造体と列挙体 {#structs}
// ❌ ベア構造体。どこでも `struct` キーワードが必要
struct point { int x, y; };
struct point p = {1, 2};
// ✅
typedef struct { int x, y; } Point;
Point p = {1, 2};
// ❌ 初期化されていない構造体
Point p;
use(p.x); // 未定義動作
// ✅
Point p = {0};
// ❌ マジック整数定数
if (state == 3) { ... }
// ✅
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_DONE } State;
if (state == STATE_DONE) { ... }
6. プリプロセッサとヘッダ {#preprocessor}
// ❌ インライン関数で代用できるマクロ(型安全性なし、二重評価)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(x++, y) // x > y の場合、xが2回インクリメントされる
// ✅
static inline int max_int(int a, int b) { return a > b ? a : b; }
// ❌ インクルードガードなし
// my_header.h
struct Foo { int x; };
// ✅
#ifndef MY_HEADER_H
#define MY_HEADER_H
struct Foo { int x; };
#endif
// または: #pragma once(広くサポートされているが標準ではない)
マクロは条件付きコンパイルと定数のために使う。ロジックには static inline を使用する。
7. C言語特有のアンチパターン {#antipatterns}
| アンチパターン | 推奨される方法 |
|---|---|
sprintf | バッファサイズを指定した snprintf |
gets | fgets(getsはC11で削除) |
malloc の結果をキャスト | 暗黙の void* 変換に任せる |
malloc内の sizeof(Type) | sizeof *ptr |
| 大規模/ランタイム配列にVLA | ヒープ割り当て |
コンテキストパラメータなしの void* コールバック | 関数ポインタと一緒に void *ctx を渡す |
| グローバル可変状態 | 構造体ポインタを介して状態を渡す |
ランタイムエラーハンドリングに assert | 適切なエラーリターンコード |
読み取り専用のポインタパラメータに const がない | const char *str |
| 比較で符号あり/なしが混在 | 一貫した型を使用し、明示的にキャスト |
制限事項
- これらは言語固有のガイドラインであり、全体的なアーキテクチャ上の決定を網羅するものではありません。
- 過度な圧縮は可読性を損なう可能性があるため、判断を適用してください。