C言語のコードは、一度書いて終わりではありません。
後から読み返す自分やチームメンバーが理解しやすいように整理されたコメントを書くことが、品質と開発スピードの両方に効いてきます。
本記事では、C言語のコメントの基本ルールから、プロが実務で使う書き方、やってはいけないNG例、さらにそのまま真似できるサンプルコードまで、体系的に詳しく解説します。
C言語のコメントの基本ルール
コメントの種類
C言語では、2種類のコメント記法が標準で用意されています。

ブロックコメント(/* … */)
/で始まり/で終わる形式です。
複数行にも1行にも使えます。
/* これはブロックコメントです。
* 複数行にわたって説明を書くときに便利です。
*/
int main(void) {
return 0;
}
ブロックコメントは、ファイル先頭のヘッダーコメントや、関数の仕様説明など、まとまった文章を書くときに向いています。
行コメント(// …)
//から行末までがコメントになります。
int count = 0; // ループ回数を数える
行コメントは、短い補足やポイントだけを書きたいときに向いています。
C99以降の標準仕様で使えますが、古いコンパイラでは利用できない場合があるため、組み込みやレガシー環境では事前に確認する必要があります。
コメントで使ってはいけない記号の注意点
ブロックコメントの/と/は入れ子にできない点に注意が必要です。
/* 外側のコメント
/* ここに別のコメントを書こうとすると */
// コンパイルエラーになる可能性が高い
*/
このようなパターンを避けるために、「既にある/* ... */の中に、さらに/*を書かない」ルールをチームで共有しておくと安全です。
どの範囲にコメントを書くべきか
コメントは多ければ良いわけではありません。
「どこに、どのレベルで」書くかを決めておくと、コードが読みやすくなります。

ファイル・モジュール単位のコメント
ファイルの先頭には、そのファイルが何を担当しているかを明記します。
/* timer.c - システムタイマ制御モジュール
*
* 概要:
* - ハードウェアタイマを初期化し、周期割り込みを提供する
* - ミリ秒単位の遅延関数を提供する
*
* 注意事項:
* - 割り込みハンドラから呼ばれる関数は再入可能であること
* - マルチスレッド環境ではロックの取得順序に注意すること
*/
このレベルでは、「細かい処理内容」ではなく、責務・制約・他モジュールとの関係を中心に書きます。
関数レベルのコメント
関数の役割や、引数・戻り値・前提条件などをまとめて説明します。
詳細は後述の「関数コメントの書き方」で具体例を示します。
ブロック(条件分岐・ループ)レベルのコメント
if文やfor文などが「なぜその条件なのか」「なぜこのループが必要なのか」が分かりにくい場合、その直前にコメントを書きます。
/* タイムアウト判定:
* - 現在時刻が開始時刻 + 500ms を超えた場合は異常とする
*/
if (now_ms > start_ms + 500) {
return ERR_TIMEOUT;
}
行レベルのコメント
行レベルのコメントは、本当に必要な場合に限るのがポイントです。
コードから読み取れる内容をなぞるコメントは避け、「理由」や「背景」に限定します。
コメントで避けるべき曖昧な表現
曖昧なコメントは、無いよりも有害になることがあります。

避けるべき曖昧な表現の代表例として、次のようなものがあります。
- 「たぶん」「おそらく」「一応」「たいてい」「とりあえず」など
- 「よく分からないが動く」「とにかくこれでうまくいった」
- 「そのうち直す」「あとで最適化する(いつか)」
これらは、責任の所在をぼかしてしまい、仕様や品質の判断を誤らせる危険があります。
代わりに、根拠・期間・出典・制約を明確に書きます。
/* 仕様上の制約:
* - プロトコル仕様書 v1.3 の 4.2節では、
* パケット長は最大 32 バイトと定義されている。
* - そのため、バッファサイズは固定で 32 にしている。
*/
#define PACKET_BUF_SIZE (32)
コメントの書き方
意図や設計方針を書くコメントの例
「なぜこの設計にしたのか」は、コードだけでは伝わりにくい部分です。
将来、別の人がリファクタリングする際に、意図が分かっているかどうかで判断が大きく変わります。

/* イベントキューの実装について
*
* 設計方針:
* - メモリフットプリントを小さくするため、固定長配列を使用する
* - 割り込みハンドラからもアクセスされるため、ロックは使わない
* - その代わり、単一プロデューサ・単一コンシューマの前提を置く
*
* 代替案:
* - リンクリスト + 動的確保は検討したが、
* 組み込み環境での断片化リスクを避けるため採用しなかった
*/
このように、採用しなかった案や制約も書いておくと、将来同じ検討を繰り返さずに済みます。
関数コメント(役割・引数・戻り値)の書き方
関数コメントは、ミニマムなドキュメントと考えると分かりやすいです。
少なくとも、次の情報を揃えておきます。
- 関数の役割(何をするか)
- 引数(意味・単位・前提条件)
- 戻り値(成功/失敗の判定方法)
- 副作用(グローバル変数の更新、外部I/Oなど)
- 注意事項(スレッドセーフかどうか、呼び出し順序など)

/* タイムアウト付き読み取り関数
*
* 概要:
* 指定されたファイルディスクリプタから、最大lenバイトまで読み取る。
* timeout_msで指定された時間内にデータが到着しない場合、タイムアウトとして扱う。
*
* 引数:
* fd - 読み取り対象のファイルディスクリプタ(有効な値であること)
* buf - 読み取りデータを書き込むバッファ(長さlen以上)
* len - 読み取る最大バイト数(0より大きいこと)
* timeout_ms - タイムアウト時間(ミリ秒)。0の場合は即時タイムアウト。
*
* 戻り値:
* > 0 - 実際に読み取ったバイト数
* = 0 - EOFに到達
* < 0 - エラー(詳細はerrnoに設定される)
*
* 注意事項:
* - この関数はスレッドセーフではない。
* - ノンブロッキングモードのfdに対しては使用しないこと。
*/
ssize_t read_with_timeout(int fd, void *buf, size_t len, int timeout_ms);
引数や戻り値の意味が自明でない場合ほど、丁寧にコメントを書くことが重要です。
変数・定数に対するコメントの付け方
変数や定数は、名前だけでは伝わりきらない情報をコメントで補います。

定数に対するコメント
/* ログファイル1行あたりの最大長(バイト)
* 仕様上、1行の長さは最大1024バイトと決められているため、
* バッファサイズは終端文字分を含めて1025にしている。
*/
#define LOG_LINE_MAX_LEN (1025)
このように、「なぜその値なのか」「どの仕様に基づくのか」を書きます。
変数に対するコメント
/* 現在の接続数。
* 接続が増減するたびに、必ずこの値を更新すること。
* 負の値にはならない前提。
*/
static int current_connection_count = 0;
また、単位が重要な場合は必ずコメントで明記するか、名前に含めます。
int timeout_ms; /* タイムアウト時間(ミリ秒) */
double speed_mps; /* 速度(m/s) */
アルゴリズムや処理手順を説明するコメント
アルゴリズムの実装は、「どう書いてあるか」よりも「何をしているか」「なぜその方法なのか」が重要です。

/* バブルソートによる昇順ソート
*
* 概要:
* - 配列の隣り合う要素を比較し、順序が逆であれば交換する。
* - これを配列の末尾まで繰り返し、末尾から順に最大値が確定していく。
*
* 選定理由:
* - 要素数が最大でも 20 個と少ないため、実装が単純なバブルソートを採用。
* - 安定ソートであることも条件の1つ。
*
* 計算量:
* - 時間計算量はO(n^2)
* - 空間計算量はO(1)
*/
void bubble_sort(int *arr, size_t n)
{
size_t i, j;
for (i = 0; i < n; ++i) {
for (j = 0; j + 1 < n - i; ++j) {
if (arr[j] > arr[j + 1]) {
/* 隣り合う要素の順序が逆の場合に交換する */
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = tmp;
}
}
}
}
コメントでは、処理の全手順を逐一説明する必要はありません。
概要・理由・特性を中心に書き、詳細はコードの読みやすさでカバーします。
TODOコメント・FIXMEコメントの使い分け
実務では、未完了の作業や既知の問題をTODOやFIXMEコメントで管理することがあります。

TODOコメント
「将来やりたい改善や未完の実装」を示します。
/* TODO(2025-03-31まで):
* - 設定値をハードコードではなく設定ファイルから読み込むようにする
* - 環境変数での上書きもサポートする
*/
int max_clients = 100; /* とりあえず固定値 */
期限や担当者をTODO(担当者, 期限)のように書くルールを決めておくと、放置されにくくなります。
FIXMEコメント
「既知のバグや問題があるが、まだ直せていない箇所」に付けます。
/* FIXME(佐藤, 2025-01-15):
* - マルチスレッド環境でデータ競合が発生する可能性がある。
* - 暫定対処としてグローバルロックを使用しているが、
* 性能劣化が大きいためロック粒度の見直しが必要。
*/
pthread_mutex_t g_lock;
FIXMEは放置してはいけないコメントなので、CIでFIXMEを検出してアラートを出す運用も有効です。
コメントのNG例と悪いパターン
コードの内容をそのまま説明するだけのコメント
「見れば分かること」をそのまま書くコメントは、ノイズになりがちです。

/* NG例 */
int i; // iという変数
i = 0; // 0を代入する
i++; // 1増やす
このレベルの説明はコードの可読性を下げるだけなので、次のように変数名で意図を表現します。
/* OK例 */
int retry_count = 0; /* リトライ回数。初期値は0回 */
retry_count++;
コメントで補う前に、まずは名前を改善することを意識します。
更新されずに嘘になるコメントのNG例
コードとコメントの内容が食い違っていると、バグの原因になります。

/* 最大3回リトライする */
#define MAX_RETRY (4)
for (int i = 0; i < MAX_RETRY; ++i) {
/* ... */
}
このような矛盾を避けるため、コメントに具体的な数値を書かない方が安全な場合があります。
/* リトライ回数:
* - 一時的な通信エラーに対して再試行する。
* - 実際の回数はMAX_RETRYで定義する。
*/
#define MAX_RETRY (4)
「変更がコード側とコメント側の両方に必要になる書き方」は極力避けるのがポイントです。
冗長で読みづらい長文コメント
説明を丁寧にしようとして、コメントが長文だらけになるのも問題です。

/* NG例: 1つの段落に長文が詰め込まれている
* この関数は、データベースからユーザ情報を取得してキャッシュに格納し、
* もしユーザが存在しない場合にはエラーコードを返すようにしていて、
* さらにログを出力して監査ログにも記録するようになっており...
*/
長くなりそうな場合は、要点を箇条書きにして行を分けることで読みやすくできます。
/* この関数の役割:
* - DBからユーザ情報を取得する
* - 結果をメモリキャッシュに格納する
* - ユーザが存在しない場合はエラーコードを返す
* - 監査ログにアクセス履歴を記録する
*/
1行の長さは80文字前後を目安に折り返すと、IDEやレビュー画面でも見やすくなります。
バグの原因になる誤解を招くコメント
コメントが実際の挙動と違うイメージを与えると、テストやリファクタリングでミスを誘発します。

/* この関数はスレッドセーフである */
static int counter = 0;
int increment_counter(void)
{
return ++counter; /* 実際にはロックしていない */
}
このようなコメントは「嘘の仕様書」と同じです。
実際の挙動に自信がない場合は、次のように書き方を変えます。
/* 現状、この関数はスレッドセーフではない。
* 将来的にマルチスレッド対応が必要になった場合は、
* counterへのアクセスにミューテックスを導入すること。
*/
断言口調で書くときは、テストやコードを見て事実であることを確認してからにします。
メンテナンスしにくいコメントスタイルの例
コメントにもスタイルがあります。
一貫性のないスタイルはメンテナンス性を下げます。

NGパターンの例として、次のようなものがあります。
- 1つのファイルの中で
//と/* ... */の使い方がバラバラ - 関数コメントのフォーマットが毎回違う
- 日本語と英語が混在し、どちらも中途半端
- インデントや整形が崩れていて読みにくい
これらを避けるために、チームでコメントスタイルをルール化し、「関数コメントは必ずこの形式」「TODOには期限を書く」などの基準を共有しておくことが重要です。
コメントのサンプルコード集
ここからは、実際にそのまま使えるコメント付きのサンプルコードを紹介します。
良いコメント付き関数のサンプルコード

#include <stdio.h>
#include <string.h>
/* 文字列の末尾にサフィックスを追加するユーティリティ関数
*
* 概要:
* - destの末尾にsuffixを連結する。
* - 連結結果がdest_sizeバイトを超える場合は、
* 超えないところまでで切り捨てる(終端の'\0'は必ず付与)。
*
* 引数:
* dest - 連結先バッファ。初期文字列が格納されていること。
* dest_size - destバッファの全体サイズ(バイト数)。
* suffix - 末尾に連結したい文字列(UTF-8を想定)。
*
* 戻り値:
* 0 - 成功(切り捨ての有無にかかわらず)
* -1 - 引数が不正(NULLポインタ、サイズ0など)
*
* 注意事項:
* - 切り捨てが発生したかどうかは戻り値では分からない。
* 将来的に判別が必要になった場合は、別APIの追加を検討すること。
*/
int append_suffix(char *dest, size_t dest_size, const char *suffix)
{
size_t len_dest;
size_t len_suffix;
if (dest == NULL || suffix == NULL || dest_size == 0) {
return -1;
}
len_dest = strlen(dest);
len_suffix = strlen(suffix);
if (len_dest >= dest_size - 1) {
/* すでにこれ以上追加できないので、そのまま終了 */
dest[dest_size - 1] = '\0';
return 0;
}
/* 追加できる最大バイト数を計算する(終端の'\0'を除く) */
size_t max_append = dest_size - 1 - len_dest;
/* 実際にコピーするバイト数(切り捨てが発生する可能性あり) */
size_t copy_len = (len_suffix < max_append) ? len_suffix : max_append;
memcpy(dest + len_dest, suffix, copy_len);
dest[len_dest + copy_len] = '\0';
return 0;
}
int main(void)
{
char buf[16] = "Hello";
append_suffix(buf, sizeof(buf), ", world!");
printf("%s\n", buf);
return 0;
}
Hello, world!
このサンプルでは、関数コメントで「仕様」「制約」「将来の拡張ポイント」を明確にしています。
エラーハンドリングのコメント例
エラーハンドリングは、「なぜこのように扱うのか」をコメントしておくと、意図が伝わりやすくなります。

#include <stdio.h>
#include <errno.h>
#include <string.h>
/* 設定ファイルを開き、FILEポインタを返す
*
* エラー時の扱い:
* - ファイルが存在しない場合(ENOENT):
* デフォルト設定で起動し、警告ログのみ出力する。
* - 権限エラー(EACCESなど):
* 起動を中止し、ユーザにメッセージを表示する。
*/
FILE *open_config_file(const char *path)
{
FILE *fp = fopen(path, "r");
if (fp == NULL) {
int err = errno;
if (err == ENOENT) {
/* 設定ファイルが存在しない場合:
* - デフォルト設定で起動することを想定。
* - ログレベルWARNで警告を出しておく。
*/
fprintf(stderr,
"Warning: config file '%s' not found. "
"Using default settings.\n",
path);
return NULL;
}
/* それ以外のエラーは致命的とみなし、起動を中止する。 */
fprintf(stderr,
"Error: failed to open config file '%s': %s\n",
path, strerror(err));
return NULL;
}
return fp;
}
このように、エラーごとの方針をコメントに残しておくと、別の開発者が挙動を変えようとするときに判断しやすくなります。
組み込み向けCコードのコメント例
組み込みCでは、ハードウェア依存の情報をコメントでしっかり補う必要があります。

#include <stdint.h>
/* タイマーモジュールのベースアドレス
* Datasheet: XYZ Microcontroller Timer v2 (Table 3-1)
*/
#define TIMER_BASE_ADDR (0x40000000UL)
/* タイマー制御レジスタ(TCR)のオフセット
* bit0: enable (1=有効, 0=無効)
* bit1: mode (0=one-shot, 1=periodic)
* bit2: irq_en (1=割り込み有効, 0=無効)
*/
#define TIMER_TCR_OFFSET (0x00U)
/* タイマー周期レジスタ(TPR)のオフセット
* - 単位は1クロックサイクル
* - 実際のタイマー周期は TPR / Fclk で決まる
*/
#define TIMER_TPR_OFFSET (0x04U)
#define TIMER_TCR (*(volatile uint32_t *)(TIMER_BASE_ADDR + TIMER_TCR_OFFSET))
#define TIMER_TPR (*(volatile uint32_t *)(TIMER_BASE_ADDR + TIMER_TPR_OFFSET))
/* 1ms間隔でタイマー割り込みを発生させる初期化関数
*
* 引数:
* sys_clk_hz - システムクロック周波数(Hz)
*/
void timer_init_1ms(uint32_t sys_clk_hz)
{
/* 1ms分のカウント値を計算
* 例: sys_clk_hz = 48MHz の場合、48,000カウントで1ms
*/
uint32_t counts_per_ms = sys_clk_hz / 1000U;
TIMER_TPR = counts_per_ms;
/* タイマー有効 + 周期モード + 割り込み有効 */
TIMER_TCR = (1U << 0) | (1U << 1) | (1U << 2);
}
ここでは、レジスタの意味・ビット配置・データシートへの参照をコメントで明確にしています。
チーム開発で使えるコメント規約サンプル
最後に、チームで共有できるコメント規約のサンプルを示します。

【Cコメント規約サンプル(抜粋)】
1. 言語
- コメントは原則として日本語で記述する。
- 外部公開APIやプロトコル仕様に関するコメントのみ英語とする。
2. コメント記法
- 関数コメント、モジュール概要コメントにはブロックコメント(/* ... */)を使用する。
- 行単位の短い補足には行コメント(// ...)を使用する。
3. 関数コメントのテンプレート
- 形式:
/*
* 概要:
* - ...
*
* 引数:
* arg1 - ...
*
* 戻り値:
* 0 - ...
* <0 - ...
*
* 注意事項:
* - ...
*/
- 全ての外部公開関数(API)には関数コメントを必須とする。
4. TODO / FIXME コメント
- 書式:
- TODO(担当者, 期限): 説明...
- FIXME(担当者, 期限): 説明...
- 期限のフォーマットはYYYY-MM-DDとする。
- FIXMEはリリース前に0件であることをCIでチェックする。
5. 禁止事項
- コードの内容をそのまま説明するコメント(「iをインクリメントする」など)。
- 曖昧な表現(「たぶん」「おそらく」「一応」など)。
- 日本語と英語が混在した中途半端なコメント。
このような規約を最初に決めておくことで、レビューの軸がはっきりし、コメント品質のばらつきも減らせます。
まとめ
C言語のコメントは、単なる「メモ」ではなく、将来の自分や他人への正式なコミュニケーション手段です。
どの範囲に何を書くかを意識し、コードからは読み取れない「意図」「背景」「制約」「設計方針」を中心にコメントすることで、保守性と信頼性が大きく向上します。
NG例で見たような「嘘のコメント」「冗長すぎるコメント」を避けつつ、チームで統一したルールを運用すれば、コメントは強力な開発支援ツールになります。
本文とサンプルコードを参考に、日々のC言語開発に適切なコメントスタイルを取り入れてみてください。
