C言語のバグ回避をするための習慣:私の場合

こんな記事がアップされていた。
C言語のバグ回避をするための習慣 - Qiita
「へぇ」「ふーん」という感じに眺めていた訳だが。

自称職業Cプログラマ*1の自分の場合はどうだろうか? 実は、保守的ながらも意外とツールに頼っている。機械に任せられるところは任せてしまい、もっと集中すべき所に資源を投資しましょうや。

というわけで、組み込み系のクロスプラットフォームなライブラリ屋の習慣の一例:

C/C++コンパイラの警告 ≒ 他の言語のコンパイルエラー/実行時エラー

はいここテストに出まーす。

C/C++コンパイラが吐き出す警告は、特段の理由がない限り見敵必殺ですな。理由を調べて、しかるべき適切な処置をとるのが、よく訓練されたCプログラマだと思っている。

他の言語ではコンパイルできなさそうなケースでも、CやC++では警告で済んでしまうことは多い。ホント、Cプログラミングは地獄だぜ!

とりあえず、開発初期からこまめにコンパイルする

本職がクロスプラットフォームのライブラリ屋なこともあり、Visual StudioXcodeのようなプロジェクト管理の仕組みのない、本当にまっさらなところからコードを書き始めることも多い。

それでも、開発の比較的初期の段階からMakefileを書いて、ソース単体のコンパイルぐらいは手軽に繰り返し実行できるようにしている。ライブラリや実行ファイルをビルドする、となると敷居が高くなることもあるが、単に個々のファイルをコンパイルする(オブジェクトコードを生成する)だけなら、関連するヘッダファイルさえ用意できれば比較的簡単に実施できる。

ソースコード自体も、極力コンパイル可能な状態を保つように書いていく。

こうすると、頻繁にコンパイラによるチェックを実施できるようになる。後は、コードを書きつつちょくちょくコンパイルを行い、まめに警告を確認して潰していくだけだ。

とりあえず、コンパイラの警告レベルを挙げる

私の場合、クロスプラットフォームなC89〜C95のコードを書きたいので、gccやclangでは「-Wall -ansi -pedantic」が最低基準となる。コンパイラによるチェックを行う場合は、この他に24〜25個ぐらいオプションを付けている。

Visual StudioC/C++コンパイラなら、警告レベル4(/W4)を使う。本当は警告をすべて有効(/Wall)にしたいのだが、Microsoft謹製のヘッダファイルが原因で警告が出るので……。

とりあえず、複数のコンパイラでチェックする

開発PCがWindowsなので、基本的にはTDM-GCCVisual Studioの両方でチェックを行う。時々LinuxgccMac OS Xのclangでもチェックしている。

コンパイラによって報告される警告が異なることも多いので、個人的にはおススメ。

余裕があるなら静的解析ツールをセットで使う

最近サボり気味だが……以前はSplintやCppcheckも使っていた。今もHDDの中に眠っている。

Splintといえばコメント文によるアノテーションが有名だが、それを使わなくとも、複数のコンパイラによるクロスチェック的な効果が得られる。Cppcheckは言わずもがな。ただどちらも、本当にいい感じに使うには、ある程度設定する必要があるからなあ。

条件判定時の変数の位置

条件判定では「何が」条件に合致しているか明確にしたいので、変数は二項演算子の左側に置いている。

if (c == EOF) {
    return;
}

ぱっと見で「『cが』EOFと等しい」という風に理解しやすいと感じる。

次のように書き間違えてしまったとしても、数種類のコンパイラのうちどれかで警告される。単体テストでNGとなる可能性もあるだろう。

if (c = EOF) {
    return;
}

もし代入を絡めた式を使いたいのならば、代入とは別に、明示的に比較演算子を書く。

if ((c = getchar()) == EOF) {
    return;
}

このような書き方の是非は一旦置いておくとして、しかし少なくともこのように書く習慣を身につけておけば、コードリーディングにて「if (c = EOF)」というコード片を見つけた段階で、迷うことなく「これはバグだ」と判断できる。

あと、例えば値が一定の範囲内であることを確認する場合も、「何が」に注目して、次のように記述している。

if ((x >= X_MIN) && (x <= X_MAX)) {
    do_something(x);
}

「X_MIN ≦ x ≦ X_MAX」に似ている書き方が推奨されることがあるが、慣れの問題なのか、「『x が』X_MIN 以上である」という条件を読み取るのに時間がかかってしまう。なので、個人的には避けている。

if ((X_MIN <= x) && (x <= X_MAX)) {
    do_something(x);
}

公開関数は明示的な引数チェック、枝葉のstatic関数はassert(3)で引数チェック

これは賛否が分かれるところだが、他人が使用するライブラリを書く時の、私なりの入力チェック位置について。

  • 他人に公開する関数(いわゆるヘッダファイルでexternする関数)では、引数の値を明示的にチェックし、不正な値だったらreturnしてしまう。
    • できれば戻り値などでエラーを通知する。
  • ライブラリ内でのみ使用する非公開関数では:
    • 末端の関数では、assert(3)を使って「期待する値が設定されたこと」をチェックする。
    • 末端の関数を呼び出す上位階層側で、責任を持って不正な値のチェックや除去を行う。

Cライブラリのassert(3)を使うこともあれば、環境依存のアサーション関数を使うこともある。

公開関数での明示的な引数チェックは不可欠だ。妙な値が突っ込まれてもライブラリがひっくり返らないようにするには、事前に入力をバリデーションしてしまうのが手っ取り早い。

一方でライブラリ内部では、関数の呼び出し階層の上位にて不正な値をチェックすることで、下位層の関数にて不正な値のチェックが不要となる(その結果、実装が若干シンプルになる)ことが多い。いつまでもいつまでもズルズルと不正値のチェックを引きずるのは、良くない習慣だ。

とはいえ上位層でチェック漏れがあるかもしれないので、assert(3)を仕掛けておく。こうしておけば、単体テスト結合テスト境界条件をチェックする際に、問題があればプログラムが落ちる。

つまり、この方法の前提として、関数単位での単体テストや、ライブラリ単体での結合テストを実施することで、品質を確保する必要がある。

なおリリース時には、NDEBUGを定義するなどしてassert(3)を消すので、消えても問題ないように注意して使用しなくてはならない。

ローカル変数は使用直前に初期化する

これも賛否が分かれるところだが、ローカル変数を定義時に必ず初期化するのは止めて、その変数を初めて使用する直前で初期化するようにしている。

size_t i;       /* 初期化していない */

/* 中略 */

/* ここで使用直前に初期化。
 * C++やC99以降なら for (size_t i = 0; ...) と書けるのだが……。
 */
for (i = 0; i < sizeof(buf); i++) {
    do_something(i, buf[i]);
}

もし、その変数が関数の戻り値を取得するためのものであり、且つそのパスを確実に通過するのならば、事前の初期化無しで使用することになる。

ctx_t *ctx;     /* 初期化していない */

/* 中略 */

/* 初期化せずに、関数find_ctx_from_id()の戻り値取得に使用 */
ctx = find_ctx_from_id(id);
if (ctx == NULL) {
    return false;
}

理由は4つある。

  1. コンパイラ/警告レベルによっては、ローカル変数の初期化忘れを検出できるため。
    • 静的解析ツールでもOK。
  2. 未使用の変数を検出するため。
  3. 変数の寿命を明確化するため。
  4. 「偶然いい感じに初期化していた」ために問題が起こらなかったのか、それとも意図通りだったのか、不明瞭であるケースを排除するため。

手元のTDM-GCCgcc 4.8.1)やVisual Studio(2005、2013)では、ローカル変数を未初期化の状態で参照するコードを意外と検出してくれる。こまめに警告を潰していれば、変数の初期化忘れでバグを作りこむ可能性は小さくなる。

コンパイラによっては未使用の変数を検出する機能があるが、変数定義時に初期化していると、この機能が働かないことがある。最近は不明だが、以前は初期化していても検出できたのはclangぐらいだったと思う。

変数の寿命に関しては『Code Complete 第2版 上』を参照のこと。ブロックの先頭で変数の定義と初期化を行ってしまうと、その変数の寿命の開始位置がブロックの先頭であるかのように見えてしまう。しかし、その変数を初めて使用する位置の直前で初期化すれように徹底すれば、他の言語にてローカル変数を使用する直前で定義する場合のように、変数の寿命の開始位置が明確となる。

最後に、例えばローカル変数を定義位置で機械的にゼロクリアしていると、後でコードを読む他人からすれば、「とりあえずゼロクリア」なのか「何らかの理由があり、それを把握した上でゼロクリア」なのか、判断に苦しむことになる。要注意。

構造体は、専用の関数か定数(もどき)で初期化する

私の場合、構造体の初期化が「とりあえずゼロクリア」で済むケースが皆無なこともあり、メンバを初期化する関数を定義しておき、使用直前に初期化関数で初期化することが多い。この方法は、例えば構造体Aの中に構造体Bがあるケースにて、構造体Aの初期化関数を書く時に構造体Bの中身を気にせずに済む(構造体Bの初期化関数を呼べばよい)ので楽だ。

もしくは適切な初期値を配置したstatic constな変数を用意しておき、それを代入することもある。

どちらの方法も、複数の場所にて、容易に同じ初期値の構造体変数を用意できる。構造体配列の初期化にも流用できる*2

ついでにいえば、ライブラリに初期化関数と終了関数がある場合、何度も初期化と終了を実行されても問題ないように実装する必要がある。ここで、例えば初期化関数内にてstaticな構造体変数のメンバの値を初期状態にクリアしたいなら、変数宣言時の初期化子だけではどうにもならない。この辺の事情が、専用の関数かstatic constな変数で初期化する習慣の一因となっている。

まあ、C99以降の指示初期化子や複合リテラルが使えるなら、この辺のスタイルも変化するだろうけど……仕事で使えるようになるのは、当分先だろうなあ。

sizeofは型名ではなく変数名で

『プログラミング作法』P.43より。

ローカル配列のサイズを表す名前をわざわざ考えてもそれほど意味はないが、サイズや型が変化しても変更する必要のないコードを書くのは間違いなく意味のあることだ。

型を変更したときに付随する変更点を減らすため、sizeofには型名ではなく変数名を指定している。1つ1つは微々たる物だろうが、塵も積もれば山となる、だ。

あと「『何』のサイズか?」という点を明確にしたい、という理由もある。変数xがint型なのかlong型なのかは興味の対象外であり、あくまでも「変数x」のサイズである点を主張したいので、「sizeof(x)」と書く。

もちろんポインタ経由の場合は若干の注意が必要で、例えばポインタ変数ptの参照先の大きさを求めたいなら、デリファレンスして「sizeof(*pt)」と書く必要がある(ただし配列を除く)。

配列の要素数の取得は定数ではなく「(sizeof(array) / sizeof( (array)[0]) )」で

これも『プログラミング作法』から学んだ。

配列の要素数を定数化して役割を明確にしたり調整可能にしたりすることが重要であるケースは多いが、それが全てではない。

また、配列を走査する時、配列の要素数を意味する個別の定数名を常に気にしなくてはならないのは面倒だ。

「(sizeof(array) / sizeof( (array)[0]) )」という風に展開する関数マクロを使用することで、配列の要素数が直値だろうと、配列の要素数を意味する定数名が何だろうと、気にせずに配列名から要素数を得ることができる。

ただし、この方法は宣言が可視である配列にしか通用しない(宣言が不可視だったり、ポインタだったりした場合は、誤った値を返すはず)。その点には注意する必要がある。

……C++では関数マクロではなくテンプレート関数による実装がある。またWindows APIには_countofいうマクロもある。これらならポインタ等を突っ込まれた時にコンパイルエラーになったはず(うろ覚え)。まあC++の場合は(特に制限が無いならば)std::vectorやstd::arrayを使っちゃうだろうな。

変数の型と大きさに気をつける

暗黙の型変換があるので忘れがちだが、標準ライブラリ関数のマニュアルをよく見てみると、「とりあえずint型で」や「とりあえず"%d"で」という態度が実に間違っていることに気がつくと思う。

for文でint型の変数をsizeofの値と比較していたり、strlen(3)の戻り値をint型の変数で受けていたり、long型の変数を"%d"でprintf(3)で表示していたり、平気でsignedな変数とunsignedな変数とで比較演算してたりするコードは好かんとですばい。

せめてsize_tぐらいは、時々でいいから思い出してください。ptrdiff_tは知らね*3

あと、CやC++はプリミティブ型の大きさすら環境依存という酷い言語なので、大きさを気にしたいなら、C99以降のstdint.hを使うか、指定した幅を持つ整数型を独自にtypedefして使うべきだろう。

大甘に見積もっても、既に64bit環境のWindowsMac OS Xとでlong型の大きさが異なるからなあ。

リソース獲得・解放の対称性に留意する

例えば、ある関数にて正常終了時に3つの何らかのリソースを獲得しているなら、その関数と対になる別の関数を定義し、その中で3つのリソースを解放する処理を実装する。

こうすることで、malloc(3)/free(3)と同様に、リソース獲得/解放のペアの関数ができあがる。上位階層では、リソース獲得関数を実行したならば、後で対になるリソース解放関数を呼べばよい。

また、関数の呼び出し階層のある層にてリソースを獲得したなら、そのリソースの解放は(関数自体は別であっても)同じ階層で行うようにした方が分かりやすい、というケースがそこそこ多い。設計や実装の際に、その点に留意したほうがよい。

エラー時のリソース解放に、時にgotoを使う

これまた賛否が分かれる話だ。

ライブラリを書いていると、時間的/手順的強度な初期化関数を用意して、その中で予め必要なリソース群を獲得する、というケースがある。

ここで、例えば5つのリソースを獲得するとき、3つ目のリソース獲得に失敗したので処理を打ち切りたいとする。先に獲得済みの2つのリソースは解放しなくてはならない。

このようなケースでのアプローチはいくつかあるが、最近では目を瞑ってgotoを使ってリソース解放処理を1ヶ所にまとめている。

例えばfree(3)のように、引数に「無効」を意味する値を突っ込まれても安全*4なリソース解放関数であるなら、gotoのラベルを1つにして次のように書く。

static foo_t foo = FOO_INVALID;
static bar_t bar = BAR_INVALID;
static baz_t baz = BAZ_INVALID;
static qux_t qux = QUX_INVALID;
static quux_t quux = QUUX_INVALID;

int initialize_module(void)
{
	foo = initialize_foo();
	if (foo == FOO_INVALID) {
		goto ERROR;
	}

	bar = initialize_bar();
	if (bar == BAR_INVALID) {
		goto ERROR;
	}

	baz = initialize_baz();
	if (baz == BAZ_INVALID) {
		goto ERROR;
	}

	qux = initialize_qux();
	if (qux == QUX_INVALID) {
		goto ERROR;
	}

	quux = initialize_quux();
	if (quux == QUUX_INVALID) {
		goto ERROR;
	}

	return 0;

ERROR:
	finalize_qux(qux);
	qux = QUX_INVALID;
	finalize_baz(baz);
	baz = BAZ_INVALID;
	finalize_bar(bar);
	bar = BAR_INVALID;
	finalize_foo(foo);
	foo = FOO_INVALID;

	return -1;
}

エラー時に毎度個別に解放処理を記述したり、またはif文のネストにしたりするよりも、こちらの方が解放処理が1ヶ所に集中するので見やすいし、解放処理の漏れによるリソースリークの危険性も減る。あとリソースの獲得と解放が1つの関数にまとまっている点も見逃せない(少なくともこのケースでは、下手に関数を分けてしまうよりは、1つの関数に収まっている方が理解しやすい、と個人的に考えている)。

ただし、引数に「無効」を意味する値を突っ込まれるとひっくり返るようなリソース解放関数である場合、gotoのラベルを1つにすると、次のように書く必要があって若干わずらわしい。

static foo_t foo = FOO_INVALID;
static bar_t bar = BAR_INVALID;
static baz_t baz = BAZ_INVALID;
static qux_t qux = QUX_INVALID;
static quux_t quux = QUUX_INVALID;

int initialize_module(void)
{
	foo = initialize_foo();
	if (foo == FOO_INVALID) {
		goto ERROR;
	}

	bar = initialize_bar();
	if (bar == BAR_INVALID) {
		goto ERROR;
	}

	baz = initialize_baz();
	if (baz == BAZ_INVALID) {
		goto ERROR;
	}

	qux = initialize_qux();
	if (qux == QUX_INVALID) {
		goto ERROR;
	}

	quux = initialize_quux();
	if (quux == QUUX_INVALID) {
		goto ERROR;
	}

	return 0;

ERROR:
	if (qux != QUX_INVALID) {
		finalize_qux(qux);
		qux = QUX_INVALID;
	}
	if (baz != BAZ_INVALID) {
		finalize_baz(baz);
		baz = BAZ_INVALID;
	}
	if (bar != BAR_INVALID) {
		finalize_bar(bar);
		bar = BAR_INVALID;
	}
	if (foo != FOO_INVALID) {
		finalize_foo(foo);
		foo = FOO_INVALID;
	}
	return -1;
}

こういう場合は、さらに目を瞑って、gotoのラベルを複数用意することにしている。

static foo_t foo = FOO_INVALID;
static bar_t bar = BAR_INVALID;
static baz_t baz = BAZ_INVALID;
static qux_t qux = QUX_INVALID;
static quux_t quux = QUUX_INVALID;

int initialize_module(void)
{
	foo = initialize_foo();
	if (foo == FOO_INVALID) {
		goto ERROR_FOO;
	}

	bar = initialize_bar();
	if (bar == BAR_INVALID) {
		goto ERROR_BAR;
	}

	baz = initialize_baz();
	if (baz == BAZ_INVALID) {
		goto ERROR_BAZ;
	}

	qux = initialize_qux();
	if (qux == QUX_INVALID) {
		goto ERROR_QUX;
	}

	quux = initialize_quux();
	if (quux == QUUX_INVALID) {
		goto ERROR_QUUX;
	}

	return 0;

ERROR_QUUX:
	finalize_qux(qux);
	qux = QUX_INVALID;
ERROR_QUX:
	finalize_baz(baz);
	baz = BAZ_INVALID;
ERROR_BAZ:
	finalize_bar(bar);
	bar = BAR_INVALID;
ERROR_BAR:
	finalize_foo(foo);
	foo = FOO_INVALID;
ERROR_FOO:
	return -1;
}

基本的にgotoは使わない派だが、例外的に上記のケースとループ内にswitch文がある場合のみ、使用している。C言語の制御構文はシンプルで、最近の他の言語に備わっている機能*5が無かったりするので、時にはgotoを使わざるをえない。

文字列配列のサイズを意味する定数のポリシーを明確にする

文字列配列のサイズを意味する定数について、最低でも以下の点を明確にした上で、コメントとして追記しておくべきだ。

  • 単位はbyteなのか、文字数なのか?
  • ヌル文字分を含めるか否か?

その上で、少なくともヌル文字に関しては、全ての定数にてポリシーを統一するべきだ。

ちなみに私はヌル文字分を含めない派。単位はその時々で使い分ける:

/** 名前の最大長(単位:byte、ヌル文字分は含まない) */
#define MAX_NAME 32

/* 中略 */

char buf[MAX_NAME + 1];

/* 中略 */

memset(buf, 0, sizeof(buf));

大きさはsizeofで取得するから(単位がbyteなら)問題ない。異論は認める。

含めるにしろ含めないにしろ、重要なのは「統一した上でコメントで書き残す」ことだ。

文字列配列のサイズを引数にとる関数のポリシーを明確にする

サイズを意味する引数の単位がbyteなのか文字数なのか、またヌル文字分を含めるか否か、コメントとして追記しておくべきだ。ヌル文字に関しては、全関数にてポリシーを統一するべきだ。

ちなみに私はヌル文字分を含める派。(単位がbyteなら)配列をsizeofした値をそのまま引数に指定すればよいので。

strncpy(3)を使わない

strcpy(3)が危険だからといって、単純にstrncpy(3)に置き換えればよいものでもないのです。

詳細は『CプログラミングFAQ』の13.2を参照のこと。strncpy(3)は境界条件にてヌル終端を行わない可能性があるので、気をつけないとバグの元になる。またコピー先バッファがコピーするデータよりも大きい場合、その分だけ勝手にゼロクリアするので*6、場合によってはボトルネックとなる可能性もある。

strncpy(3)を安全に使おうとした場合、よくある方法はmemset(3)あたりでバッファをゼロクリアしてからコピーする、というものだ。

memset(dst, 0, sizeof(dst));
strncpy(dst, src, sizeof(dst) - 1);

しかしこの方法は、memset(3)でバッファを先頭から末尾まで一度舐めた後で、strncpy(3)でもう一度先頭からほぼ末尾まで舐める。例え実害はなくとも、個人的には「いや、それってどうなのよ」と言いたくなる。

厳密に考えれば、memset(3)で全クリアする必要はない。最後の要素にヌル文字を入れておけば十分だ。

dst[sizeof(dst) - 1] = '\0';
strncpy(dst, src, sizeof(dst) - 1);

とはいえ、内部で勝手にゼロクリアするのは具合が悪い……というケースもある。

『CプログラミングFAQ』では、代替案としてstrncat(3)を使う方法が挙げられている。この方法なら確実にヌル終端されるし、残り領域をゼロ埋めすることもない。

dst[0] = '\0';
strncat(dst, src, sizeof(dst) - 1);

最初に先頭要素にヌル文字を代入すること。これを忘れると大惨事となる可能性が高い。

クロスプラットフォームなC89〜C95のコード」という縛りがなければ、代替の関数があるのだけどなあ。

マルチバイト文字の終端に注意する

マルチバイト文字というか、要は1文字のbyte数が可変である時に、固定サイズのバッファに可能な限り文字列を詰め込むコードを書く際には、単純にbyte数で切ったらダメだということ。

文字コード(符号化方式?)に応じたtruncateの処理を行うべし。うーん、面倒だ……。

ファイル末尾に1つ以上の改行を入れる

C/C++の言語規格のあるあるネタなのだが、ファイル末尾が改行でない場合の動作が未定義であることよりも、「こんな単純でつまらないことでコンパイラに警告を出されてしまうことが煩わしい」という理由の方が大きい。

しかもこれ、コンパイラの種類やバージョンやオプションによって、警告されたりされなかったりする。自分の環境では大丈夫でも、他所では警告が出たりするからなあ。

こういう細かい警告も1つ1つ丁寧に潰していかないと、ビルド時に大量の警告メッセージで溢れかえることになる。その結果、本当に重要な警告を見逃してしまったり、警告の多さに辟易してチェックが甘くなったりするものだ。

引数不要の関数の宣言・定義には必ずvoidを書く

詳細は id:eel3:20141005:1412521223 を参照。C言語C++ではないので、voidを書かないと、ちょっと問題があるのです。

C言語の規格間の差異や、C++との違いに留意する

例えば私の場合、「クロスプラットフォームなC89〜C95のコードを書く」という都合上、「//」による1行コメントは避けて、常に「/* */」スタイルでコメントを書いている。こうしないと、コンパイラの種類やオプション次第で警告が出るからなあ。ここはひとつ、保守的に。

また id:eel3:20120703:1341275935 のように、構造体定義のネストが原因で揉めたりすることもある。

C/C++でコードを書く際には、ターゲットとする環境が何で、使用できる言語規格がどれなのか確認した上で、なるべく規格に沿ったコードを書くべきだ。

前述のvoidの問題もそうなのだが――C89〜C95・C99・C11・C++は、ある意味で別物です。本当に別物なんだってば。信じてよ、お願い……だから混同するのは止めてっ!

ソースファイルの文字コードに留意する

クロスプラットフォームC/C++のコードを書く人は、どの文字コードを選択するかで苦労していると思う。

Microsoft C/C++ Compiler(Visual Studio)はロケール文字コード――日本語環境ではShift_JIS(CP932)――の他に、BOM付きUTF-8と、UTF-16(BOMの有無やエンディアンは制限なし)に対応している。ただし、「#pragma execution_character_set」を使って実行時の文字コードを指定しない限り、マルチバイト文字はロケール文字コードに、ワイド文字はUTF-16トルエンディアンに変換されるようだ。

MinGWgccLLVM-GCC含む)では、標準ではUTF-8に対応している。以前はBOM無しである必要があったようだが、最近ではBOM付きに対応している。「-finput-charset」でソースファイルの文字コードを、「-fexec-charset」で実行時の文字コードを明示することも可能だ。

clangは、残念ながらUTF-8にしか対応していない。BOMの有無に関しては、最近ではどちらもOKなようだ。

コンパイラMicrosoft C/C++ Compiler、MinGWgcc、clangで十分な場合、比較的最近のバージョンを使用しているなら、BOM付きUTF-8は悪くない選択だ。ただし文字列リテラルとして埋め込んだ文字の文字コードに関しては、若干の注意が必要だろう。少なくとも、実行時に文字列リテラルUTF-8である、と考えてはならない。

他のコンパイラのことも考えるならどうか? 古いコンパイラでは、Shift_JIS(CP932)では動作検証が済んでいるが、UTF-8は不明、ということがある。BOM付きともなれば……。

この場合は、ソースファイルの文字コードとしてShift_JISを選択した上で、以下の点を順守して記述することになる。

  • 文字列リテラルにはUS-ASCIIの範囲の文字しか書かない。
  • コメントは旧来のC言語のスタイル(/* */で囲むやつ)で記述する。

どうしてもC++スタイルの1行コメント(//)を使いたい場合は、コメント文の末尾にShift_JISのダメ文字のうち0x5C(バックスラッシュ)と誤認される文字を書かないようにすること。書いてしまうと、少々興味深い現象に直面する。

どう興味深いか知りたい人は、次の内容のソースファイルをShift_JISで作成して、clangやgcc(-finput-charsetなし)でビルドしてみればよい。

#include <stdio.h>

// 表
int main(void)
{
	(void) puts("hello, world");
	return 0;
}

コメント行の次の行までコメントと見なされる危険が危ないのです。コンパイルエラーになるのはまだマシな方で、知らないうちに1行分のコードが実行されていなかったとしたら……。

ともかく、こうしてUTF-8にしか対応していないコンパイラを誤魔化して、Shift_JISのソースファイルを突っ込むのである。

そんな訳で、一言言わせていただきたい。何でXcodeからLLVM-GCCを取り除いちゃったのかなあ、Appleは! 次は、いつAndroid NDKからgccが取り除かれてclang 1本になるのか、かなあ。

まとめ

えーと、人によって結構流儀が異なるものです。コーディングルールが欲しくなるはずだよね……。

*1:色々やっているけど、半分ぐらいはC言語で食べているはず。

*2:ループ構文を使って明示的に初期化するとか。

*3:……といいつつも、1度ぐらいは仕事で使った覚えがある。

*4:C言語の規格的には、free(3)の引数がNULLでも問題ないはず。

*5:ラベル付きのbreakやcontinueとか。

*6:正確には、第2引数で指定した領域からコピー可能なbyte数が、第3引数で指定したbyte数よりも小さな場合に、第3引数で指定したサイズになるまで第1引数の残り領域をゼロ埋めする。