『Rational C Coding Convention (draft)』
$Revision: 1.11 $

$Date: 2006/02/22 09:42:03 $

contents

0. Informing

0-1 日本語エンコーディングはEUC3
0-2 改行はLFのみ4
0-3 最終行の改行コードを忘れないようにする5
0-4 ファイル名に大文字を使わない6
0-5 13区文字などを使わない6
0-6 英数字/記号はASCIIで書く6

1. Notation

1-1! インデントにはタブを使う7
1-2! 中括弧の位置8
1-3* 条件部の前の空白9
1-4 関数の括弧の前に空白をあけない10
1-5* 2項演算子の両側には1つずつ空白をあける11
1-6 sizeofは関数のように書く12
1-7 caseラベルのインデント13
1-8 ラベルのインデント14
1-9 括弧のすぐ内側に空白をあけない15
1-10 カンマの前には空白をあけない、後ろにはあける16
1-11 カンマは前の行の末尾に付ける17
1-12@ セミコロンの前に空白をあけない18
1-13 forのセミコロンの後ろには空白をあける19
1-14@ ブロックコメント20
1-15 インラインコメント21
1-16 #includeの後ろには空白をあける22
1-17 ラベルのコロンの前は空けない23
1-18 余分な括弧は付けない24
1-19 16進数は小文字で25

2. Rhetoric

2-1 (void *)やconstにキャストしない25
2-2 NULLをキャストしない26
2-3 '\0'のことをNULLと呼ばない27
2-4 '\0'やNULLを0で代用しない28
2-5 void型関数のreturn29
2-6 typedefで余分なタグを書かない30
2-7 ポインターや配列をtypedefしない31
2-8 同名のローカル変数で他の変数を隠さない32

3. Syntax

3-1 struct中で名前なしunionメンバーを使わない33
3-2 文字リテラル中に2バイト以上書かない34
3-3 #ifdefを使わない35

4. Logic

4-1 エンディアン依存のコードを書かない36
*従来のルールでも規定されているのに守られていないことが多いので強調したいもの
@従来のルールの解釈によっては相反するもの
!従来のルールで明確に規定されていることと相反するもの

0-1 日本語エンコーディングはEUC

ソースファイルの日本語エンコーディングはEUC-JPにする。 日本語エンコーディングとしてCのソースファイルに使われるものは他に、

などがあるが、これらは使わない。

Windows上のVisual C++ 6.0などでは、日本語のソースファイルはEUC-JPのものでも、 コンパイラーそのものは処理するが、 IDE環境ではShift_JIS以外は文字化けしてしまうし、 文字列リテラルはShift_JISで記述しないと実行時に正しく表示されない。 そのような環境ではShift_JISを使うのもやむをえないが、 Unix環境においてShift_JISを使う利点は1つもない。

たいがいのプロジェクトでは日本語文字に対応していない処理系との互換性 を考え、『文字列リテラルや文字リテラル中に日本語文字を直接書かない』 ことになっているので問題ないのだが、もしリテラル中に日本語文字を書く場合、 エンコーディングをShift_JISにするといろいろな問題点がある。

文字列リテラルの中にShift_JISの文字を書くことができる処理系であっても、 単に『素通し』にしているだけの対応しかしていないものは、 以下のように『2バイトめが0x5c(バックスラッシュ)である文字』を 文字列リテラル中に書いたときに、正常に処理されない。

    printf("予約表示機能");

そのような文字の直後には以下のようにバックスラッシュを追加してエスケイプする必要がある。

    printf("予\約表\示機能\");

このようにエスケイプしたとしても、そのShiftJIS文字列を適切に処理する (バックスラッシュによるエスケイプを追加しなくてもよい)処理系にそのソースを 持っていくことを考えて、#if〜#else〜#endifで両方のパターンを書く、など かなり面倒なことが必要になる。

また、現状のプロジェクトのように、コメント中にのみ日本語を使うのならば 平気かというとそうでもなく、たとえば『//』形式のコメントが許可されている場合、 そのコメント末尾に『2バイトめがバックスラッシュな文字』があると、次の行が 継続されてしまい、正常に処理されない。(場合によってはコンパイルエラーに さえならなことも多く、気づかないと危険である!)

EUC-JPならばこのような問題はまったくない。

(註: 現在なら『UTF-8で統一』という選択肢もあるだろうが、どっちにしろShift_JISを選択する理由だけは1つもない。百害あって一理無し。)

[参考] Shift_JISで2バイトめがバックスラッシュ(0x5c)になる文字の一覧

―ソЫ噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭

0-2 改行はLFのみ

テキストファイルの改行コードの種類には主に以下の3通りがある。

このうち、『LFのみ』の方式にする。

UnixとWindowsでファイル共有する場合も、 Windowsのテキストエディターはこの3種類のどれにでも設定可能なので、問題ない。

逆に、UnixでWindowsの『CR+LF』方式のファイルを編集すると、 1つの『CR』が文字として認識されてしまう。(以下の『^M』の部分がそう)

#include <stdio.h>^M
^M
int main()^M
{^M
    printf("hello, world\n")^M
    return EXIT_SUCCESS;^M
}^M

0-3 最終行の改行コードを忘れないようにする

Windows上のテキストエディターなどで編集していると、以下のように、 一番最後の行に改行コードを付け忘れることがある。

#include <stdio.h>

int main()
{
    printf("hello, world\n")
    return EXIT_SUCCESS;
}

これは実は正しいテキストファイルではない。 Windowsなどでは、融通を利かせてこのようなファイルも扱うものが多いが、 Unix上のツールではテキストファイルというものを厳密に扱うものも多いので、 以下のように、テキストファイルとして正しく、最終行にも改行コードを忘れず付ける。

#include <stdio.h>

int main()
{
    printf("hello, world\n")
    return EXIT_SUCCESS;
}

0-4 ファイル名に大文字を使わない

Windowsではファイル名の大文字/小文字は区別されない(保存はされる)環境 だったので、大文字/小文字の使い分けがいい加減なソースファイルも多かったが、 Unixではファイル名の大文字/小文字は区別されるので、 大文字を使わないように統一する。

小文字(a〜z)の26種類と数字(0〜9)および下線(_)のみの組み合わせに、 「.c」をつけたものとする。 また、先頭の1文字には数字や下線は使わず、必ずa〜zのうちどれかで始めること。

Windows環境のCコンパイラーでは、ソース中のインクルードファイルの記述で、

#include "FOO.H"

などと書いてあっても、『foo.h』という小文字のファイルをインクルードして くれたが、Unixでは当然これは『No such file.』というコンパイルエラーになる。


0-5 13区文字などを使わない

丸付き数字、ローマ数字等、JIS X 0208の13区をローカルに利用している コードは、文字列中はもちろんのころ、コメントの中にも使わない。

それらのコードは、JIS X 0208の規定の中になく、Windowsなどで独自に 割り当てているものなので、他の環境でそのソースを見たときに文字化けしてしまったり、 別の文字に見えたりするので、バグの元となる。

また、Linux上のsedなどは内部がUTF-8で処理しているため、13区文字が 混じっていると異常動作したり、終了したりする。

従来ソースでは、携帯電話での『絵文字』のコードがそのままコメント中に 書かれているものさえあった。もちろんそれも使わない。


0-6 英数字/記号はASCIIで書く

コメント中に、いわゆる『全角数字』や『全角英字』を混ぜないこと。

誤:
    /* ウィンドウIDに0を指定する*/
    /* ウィンドウIDが0ならば */

上記のように混じっていると、 同じものをエディター上で検索しながら見るときに不便なので、 以下のように使う文字を統一しておけばよい。

正:
    /* ウィンドウIDに0を指定する*/
    /* ウィンドウIDが0ならば */

記号についても、ASCIIにある32個の記号は、JIS X 0208のもの(いわゆる全角記号)ではなく、ASCIIのほうを使う。

誤: /* +入力機能 */
正: /* +入力機能 */

1-1 インデントにはタブを使う

インデントにはその深さに応じた個数のタブコード(0x09)を使う。 空白(0x20)は使わない。

標準ではタブ1個が空白8個分になる端末が多いが、これは テキストエディターの設定などで変更できるので、4個分程度に 設定しておけばよい。

Unix上のviならば、ホームディレクトリーの『.exrc』中(vimなら『.vimrc』)に 以下の設定をしておけば、タブ1つが空白4つ分で表示される。

set ts=4
set sw=4

1-2 中括弧の位置

関数定義の最も外側の中括弧は、

int func(int c, char *p)
{
    ...
}

のように書く。 (開くほうも閉じるほうも独立した1行に書き、インデントせずどちらも1桁めに書く)。

それ以外の中括弧は、

    while (i < n) {
        ...
        ...
    }

とか

    if (flag) {
        ...
        ...
    }
    else {
        ...
        ...
    }

のようにカーネルスタイル(K&Rスタイル)で書く。 (開きカッコを前の行の末尾に(空白を1つあけて)書く)。

以下のようなスタイルは使わない。


1-3 条件部の前の空白

if、while、for、switchなどの『判定条件』の部分を持つ制御のキーワードは

    if (a > 0) {

のようにキーワードに続く開き括弧との間に1つだけ空白を空けて書く。

    if(a > 0) {

というように詰めて書かないこと。


1-4 関数の括弧の前に空白をあけない

以下のようには書かない。

    printf ("hello, world\n");

以下のような書き方に統一する。

    printf("hello, world\n");

引数付きのマクロも同様。


1-5 2項演算子の両側には1つずつ空白をあける

可読性のため、2項演算子の両側には空白をあける。つまり、

    if (a==0&&b>0)
        i=j+2;

ではなく、

    if (a == 0 && b > 0)
        i = j + 2;

構造体のメンバーを参照する2種類の演算子は例外とする。

    foo->member = bar.another;

これら「->」と「.」は2項演算子でありながら、 両側のどちらにも空白をあけずに詰めて書く。

3項演算子も2項演算子と同様、それぞれの両側に空白を1つずつあけて書く。

    i = (n > 0) ? n - 1 : 0;

逆に、単項演算子の場合は被演算数とのあいだに空白を「あけない」。

    if (!flag)
        ret = -n;

つまり、以下のようには書かない。

    if (! flag)
        ret = - n;

1-6 sizeofは関数のように書く

sizeofは型名に適用する場合には括弧が必要であり、

    i = sizeof(int);

この括弧を省いたらエラーになるが、 型名ではなく、変数などのオブジェクトに対して適用する場合は実は括弧が必要ないので、 以下のように書いても文法的には正しい。

    int a[10];

    i = sizeof a;

ただ、この場合にも括弧をつけて書く以下の書き方に統一する。

    i = sizeof(a);

また、どちらの場合も、関数の場合と同じように開き括弧の前に空白をあけないこと。


1-7 caseラベルのインデント

switch文中のcaseラベルのインデントの深さはswitchの深さに合わせる (つまりswitchの内側だが、インデントレヴェルを増やさない)。 以下のように書く。

    switch (stat) {
    case UP:
        f_up();
        break;
    case DOWN:
        f_down();
        break;
    case RIGHT:
        f_right();
        break;
    case LEFT:
        f_left();
        break;
    default:
        error();
        break;
    }

defaultラベルも同様。


1-8 ラベルのインデント

goto文を使う場面はめったにないので、飛び先のラベルを使う必要も同様に少ないが、 使うときはインデント位置に注意する。

以下のように、そのラベルの存在する位置のインデントレヴェルより 『1つインデントを減らして』書く。

    while (...) {
        ...
        if (...)
            goto label;
        ...
    label:
        ....
    }

もしくは以下のように、ラベルだけは『インデント量を0に』して書く。

    while (...) {
        ...
        if (...)
            goto label;
        ...
label:
        ....
    }

1-9 括弧のすぐ内側に空白をあけない

以下のようには書かない。

    while ( n < 10 ) {

以下のような書き方に統一する。

    while (n < 10) {

1-10 カンマの前には空白をあけない、後ろにはあける

以下のようには書かない。

    func(type , wID , "string",true);

以下のような書き方に統一する。

    func(type, wID, "string", true);

1-11 カンマは前の行の末尾に付ける

長い文を複数の行に分割する場合、以下のようには書かない。

    log((LOG_USER | LOG_DEBUG)
       , "name"
       , "COMPOSITION_DEBUG : ShowCompositionWindow. DrawLen = 0\n"
       , true);

以下のような書き方に統一する。

    log((LOG_USER | LOG_DEBUG),
       "name",
       "COMPOSITION_DEBUG : ShowCompositionWindow. DrawLen = 0\n",
       true);

次の行の先頭にカンマをつける癖は、以下のような根拠が原因だと思われる。

typedef struct {
    {IM_VKEY_MENU, 0, IM_NG}
   ,{IM_VKEY_START, 0, IM_NG}
   ,{IM_VKEY_PAGE_UP, 0, IM_NG}
   ,{IM_VKEY_PAGE_DOWN, 0, IM_NG}
#if IM_KEYBD
   ,{IM_VKEY_HENKAN, IM_CT_nop, IM_NG}
#endif
} FOO;

つまり、#ifなどで条件付きコンパイルする場合、カンマを前の要素の行末に 付ける方法だと、最後の要素の場合にカンマを付けるかどうかのところが合理的に いかないというのである。しかし、逆に#if〜#endifで括る要素が1番目の要素 だったらどうだろう? と考えたときに、このやり方の矛盾に気づくであろう。

また、Cの文法ではC89の時代から、 以下のように最後の要素の後ろにカンマが付いていても問題はないことになっている。

typedef struct {
    {IM_VKEY_MENU, 0, IM_NG},
    {IM_VKEY_START, 0, IM_NG},
    {IM_VKEY_PAGE_UP, 0, IM_NG},
    {IM_VKEY_PAGE_DOWN, 0, IM_NG},
#if IM_KEYBD
    {IM_VKEY_HENKAN, IM_CT_nop, IM_NG},
#endif
} FOO;

1-12 セミコロンの前に空白をあけない

以下のようには書かない。

    printf("hello, world\n") ;

以下のような書き方に統一する。

    printf("hello, world\n");

1-13 forのセミコロンの後ろには空白をあける

    for (i = 0;i < n;i++)

ではなく、以下のようにする

    for (i = 0; i < n; i++)

1-14 ブロックコメント

関数のヘディングコメントなどは以下のような書式で書く

/*
 * strdup() - 必要なだけのメモリーを動的に確保し、文字列をそこにコピーする
 *             NULL: メモリーが確保できなかった場合
 *             NULL以外: コピーした文字列のアドレス
 */

コメントの始まりと終わりは独立した行に書き、 2桁めに「*」がくるようにそろえる。 以下のように「枠を書く」ことは避ける。

/*********************************************************************************
 * strdup() - 必要なだけのメモリーを動的に確保し、文字列をそこにコピーする       *
 *             NULL: メモリーが確保できなかった場合                              *
 *             NULL以外: コピーした文字列のアドレス                              *
 *********************************************************************************/

1-15 インラインコメント

コードと同じ行に書くコメントは、同じ行にコメントの始まりと終わりを書く。 通常のコメントの場合は、 コメントの始まりの直後、コメント終わりの直前、にはそれぞれ1つの空白をあける。

    if ((a = malloc(len + 1)) == NULL)    /* +1 は '\0' の分 */

ただし、通常のコメントではなく、Cの文法検査ツールに指示を与える コメントは、コメント始まりの後ろ、コメント始まりの前に空白をあけずに書く。

    switch (i) {
    case 1:
    case 2:
    case 3:
    case 4:
        return;
    case 5:
        val = 5;
        /*FALLTHROUGH*/
    case 6:
        break;
    }

1-16 #includeの後ろには空白をあける

以下のように書いても文法違反にはならない。

#include<stdlib.h>
#include"foo.h"

かといって、1文字分ぐらい節約したところで何の利点もない。 以下のように必ず空白をあけてファイル名を書くようにする。

#include <stdlib.h>
#include "foo.h"

1-17 ラベルのコロンの前は空けない

swichのcaseラベルやdefaultラベルに使うコロン(「:」)は、

switch (cond) {
case 'q' :
    ret = S_QUIT;
    break;
case 'w' :
    ret = S_WRITE;
    break;
default :
    ret = S_ERROR;
    break;
}

というように空白をあけず、以下のように書く。

switch (cond) {
case 'q':
    ret = S_QUIT;
    break;
case 'w':
    ret = S_WRITE;
    break;
default:
    ret = S_ERROR;
    break;
}

switchに使うラベルだけでなく、gotoの飛び先に使うラベル(めったに使う 場面はないが)も同様に、コロンの前には空白を空けない。


1-18 余分な括弧は付けない

以下のような式では、演算子の結合の優先度通りなので括弧は必要ないが、

if (0x81 <= (c) && (c) <= 0x9f || 0xe0 <= (c) && (c) <= 0xfc)

『念のため』もしくは『複雑な式なので見やすくして意図を明白するため』に 以下のように括弧を付ける分には問題ない。

if ((0x81 <= (c) && (c) <= 0x9f) || (0xe0 <= (c) && (c) <= 0xfc))

しかし、以下のような括弧は余分であり、かつ、見づらくなるだけなので、

MsbEnvironmentCreate(&(ev));

余分な括弧は付けずに、以下のように書く。

MsbEnvironmentCreate(&ev);

1-19 16進数は小文字で

16進定数の数値を書くときのa〜fは文法的には以下のように大文字で書いてもよい。

0xFF00, 0x12AB, ...

しかし、小文字で書いてある箇所と大文字で書いてある箇所が混在するのは 見づらし、大文字で書く利点もないので、Cの慣例にならって小文字に統一する。

0xff00, 0x12ab, ...

また、16進定数を示す『0x』のxは文法的には大文字でも書くことができる。

0X00

しかし、そうする利点もなく、見づらいだけなので、 小文字のx統一する。

0x00

2-1 (void *)やconstにキャストしない

たとえば以下のように、

    int func(void *);

とプロトタイプ宣言されている関数に「char *」型のポインターを 引数で渡すときに

    func((void *)p);

というようなキャストをしないこと。ふつうに

    func(p);

とだけ書く。 (ただし、『ポインターのポインター型』に代入したり比較したりする場合には、 キャストする。)

キャストを濫用するのはバグのもとなので、本当に必要な箇所でのみ行なうべきである。 そのキャストが本当に意味があるものかどうかコードを注意深く読む際に、 必要のないキャストがあちこちに出てくるのは目の邪魔である。 (本当に危ないキャスト(つまりバグの元!)を見逃す危険性を増やす)。

関数の引数に渡す場合だけでなく、「void *」型のポインターに代入する場合も同様である。

また、void *のポインターだけでなく、constに関しても同様である。 constな変数にそうでない変数を代入する場合や、constと宣言されている関数の引数に そうでない変数を渡す場合には、キャストをするべきではない。 (逆方向はエラーや警告が出るはずなので、キャストしないと通らないわけだが)


2-2 NULLをキャストしない

以下のようにはNULLをキャストしたりせず、

    foo = (BAR *)NULL;

単にNULLそのものだけにし、

    foo = NULL;

とだけ書く。

キャストを濫用するのはバグのもとなので、本当に必要な箇所でのみ行なうべきである。 そのキャストが本当に意味があるものかどうかコードを注意深く読む際に、 必要のないキャストがあちこちに出てくるのは目の邪魔である。 (本当に危ないキャスト(つまりバグの元!)を見逃す危険性を増やす)。


2-3 '\0'のことをNULLと呼ばない

CではNULLと大文字4文字で書くと、「null pointer」のことを言う文化がある。 NULLというマクロ名がそうだからだ。 したがって、文字列終端の「null character」を意味するときに以下のような コメントは不適切である。

    *s = '\0';  /* NULLを入れる */

以下の書き方ならOKである。

    *s = '\0';  /* ナル終端を入れる */

もしくは、nulと「小文字」にしたり、3文字略語としたりし、 あえて、大文字4文字のNULLは「null pointer」の意味にだけ使うようにし、 誤解を避ける。


2-4 '\0'やNULLを0で代用しない

たとえば、以下のようなコード

    if (p != NULL && *p != '\0')

を以下のように書いてもCの文法的には問題はない。

    if (p != 0 && *p != 0)

しかし、null pointerにはマクロ名「NULL」を使い、 文字列終端のnull characterには'\0'を使い、意味を明確にすべきだ。


2-5 void型関数のreturn

void型の関数定義の末尾(閉じ中括弧の直前)には以下のようなreturnを書かない。

void foo()
{
    ...
    printf("finished\n");
    return;
}
void foo()
{
    ...
    printf("finished\n");
}

2-6 typedefで余分なタグを書かない

構造体の定義その構造体のtypedefを別々に行なうのではなく、 構造体定義を直接typedefする場合、以下のようにタグ名も付けることができる。

typedef struct tagAP_INFO {
    unsigned int ApID;
    Display      *pDisplay;
    Window       Window;
} AP_INFO;

その構造体の中にその構造体自身へのポインターをメンバーとして含む場合などには、 タグ名も必要になるが、 そのように『タグ名を使う用事』が明確にある場合以外は 余分なタグを書かずに以下のように定義する。

typedef struct {
    unsigned int ApID;
    Display      *pDisplay;
    Window       Window;
} AP_INFO;

『あとで使うかもしれない』ぐらいの根拠で意味もなく付けないこと。 (従来のソースで付けてあるものがよくあったので、そう書くものだと思い込んで いちいちつける人があとをたたなかったため)。


2-7 ポインターや配列をtypedefしない

Cの文法的には配列やポインターをtypedefしてしまうこともできる。 従来のプロジェクトのコードでは以下のように、構造体をtypedefしたあと、 その構造体へのポインターをもtypedefしてあるのをよくみうけられた。

typedef struct {
    unsigned int ApID;
    Display      *pDisplay;
    Window       Window;
} AP_INFO;
typedef  AP_INFO  *P_AP_INFO;
   ...
P_AP_INFO  p_info;

このやり方では、その変数がポインターであるかどうかがわかりにくくなり、 「*」をいくつつけるべきか、また、関数引数として渡すときに「&」を付けるべきかどうかが直観的にわかりにくく、さらにはポインターの初期化しわすれや、 誤ったキャストなどのミスにつながりやすい。

この悪習はWindowsのWin32プログラミングなどの影響である可能性が高い。

型名の先頭の『P』を付けたからといってわかりやすくなるわけでもなく、 以下のように素直にポインターには「*」をつけて宣言し、使用したほうが、 明確でわかりやすいコードになる。

typedef struct {
    unsigned int ApID;
    Display      *pDisplay;
    Window       Window;
} AP_INFO;
   ...
AP_INFO  *p_info;

2-8 同名のローカル変数で他の変数を隠さない

プラットホームルールブックでは、なるべく内側のスコープでのローカル変数宣言を推奨しているが、同じ関数内に複数スコープが存在する場合、他スコープで宣言したものと同じ変数名を使用してはならない。

たとえば、以下はNG。

func()
{
    int width, height;
        :
    if (width > height) {
        short   width, height;

        getsize(&width, &height);
        if (width > height)
            :
    }
    if (width == height) {
        :
    }
}

…もはや理解不能です。こういったコーディングは絶対に止めましょう。

また、ローカル変数のネーミングは「すべて小文字で」「極力短く」することで、マクロや外部変数と一目で差別化できますので、「すべて小文字で」「極力短く」してください。

『NG例』
static long     gwidth, gheight;
        :
func()
{
    int width, height;
        :
    if (width > height) {
        short   width, height;

        getsize(&width, &height);
        if (width > height)
            :
    }
    if ((width != gwidth) && (height != gheight)) {
        gwidth = width;
        gheight = gheight;
    }
}
『OK例』
static long     gWidth, gHeight;
        :
func()
{
    int width, height;
        :
    if (width > height) {
        short   w, h;

        getsize(&w, &h);
        if (w > h)
            :
    }
    if ((width != gWidth) && (height != gHeight)) {
        gWidth = width;
        gHeight = height;
    }
}

3-1 struct中で名前なしunionメンバーを使わない

gccの拡張文法では、以下のような構造体が定義可能である。

typedef struct {
    unsigned short wVEvent;
    unsigned short wLastVEvent;
    unsigned short wMojiCode;
    ...
    union {
        FepCtrlAttr    *fepAttr;
        T_IMCT_INFO    *ptIMControlInfo;
        T_IMLB_INFO    *ptIMLBInfo;
        T_IMCT_KKCINFO *im_kkc;
    };
    ...
} IMINFO;

これは、Cの標準規格では文法違反でエラーになる(C89だけでなくC99でも)。 したがって、以下のようにメンバー名を付ける。

typedef struct {
    unsigned short wVEvent;
    unsigned short wLastVEvent;
    unsigned short wMojiCode;
    ...
    union {
        FepCtrlAttr    *fepAttr;
        T_IMCT_INFO    *ptIMControlInfo;
        T_IMLB_INFO    *ptIMLBInfo;
        T_IMCT_KKCINFO *im_kkc;
    } atr;
    ...
} IMINFO;

3-2 文字リテラル中に2バイト以上書かない

EUC-JPやShiftJISのマルチバイト文字を、文字リテラル(『"』ではなく『'』 で括るもの)中に直接書くことができる処理系もある。 互換性の見地からはそれ自体好ましい書き方ではないが、 問題は、それが使えない処理系に移植した場合に、文字リテラルはそのまま使い 中だけ『\x』のあとに16進数で書くような対処の仕方である。 Cの文法では、文字リテラル中に2バイト以上書いた場合のバイトスワップの有無は 『処理系依存』ということになっている。 したがって、同じリトルエンディアンのCPU用でも、 以下のようにバイトスワップの有無を意識した#if〜#else〜#endifで区切られた 2通りの記述をわざわざ書かなければいけなくなる。

const unsigned short voicable[] = {
#if WIN32
   '\xa9\x82', '\xab\x82', '\xad\x82', '\xaf\x82', '\xb1\x82', '\x4a\x83',
   '\x4c\x83', '\x4e\x83', '\x50\x83', '\x52\x83', '\xb3\x82', '\xb5\x82',
   '\xb7\x82', '\xb9\x82', '\xbb\x82', '\x54\x83', '\x56\x83', '\x58\x83',
   '\x5a\x83', 0x835c,     '\xbd\x82', '\xbf\x82', '\xc2\x82', '\xc4\x82',
   '\xc6\x82', '\x5e\x83', '\x60\x83', '\x63\x83', '\x65\x83', '\x67\x83',
   '\xcd\x82', '\xd0\x82', '\xd3\x82', '\xd6\x82', '\xd9\x82', '\x6e\x83',
   '\x71\x83', '\x74\x83', '\x77\x83', '\x7a\x83', '\x45\x83' ,'\xa4\x82'
#else
   '\x82\xa9', '\x82\xab', '\x82\xad', '\x82\xaf', '\x82\xb1', '\x83\x4a',
   '\x83\x4c', '\x83\x4e', '\x83\x50', '\x83\x52', '\x82\xb3', '\x82\xb5',
   '\x82\xb7', '\x82\xb9', '\x82\xbb', '\x83\x54', '\x83\x56', '\x83\x58',
   '\x83\x5a', 0x835c,     '\x82\xbd', '\x82\xbf', '\x82\xc2', '\x82\xc4',
   '\x82\xc6', '\x83\x5e', '\x83\x60', '\x83\x63', '\x83\x65', '\x83\x67',
   '\x82\xcd', '\x82\xd0', '\x82\xd3', '\x82\xd6', '\x82\xd9', '\x83\x6e',
   '\x83\x71', '\x83\x74', '\x83\x77', '\x83\x7a', '\x83\x45' ,'\x82\xa4'
#endif
};

もともとこれは2バイトshortのコードを入れているだけなのだから、 文字リテラル中にShiftJISの文字を直接埋め込むことができなくなった時点で 文字リテラルの利点はなくなるのだから、わざわざ#ifを使ってまでそれに こだわる必要はない。 以下のように文字リテラルを使わずに書けば、#if〜#else〜#endifも必要なくなる。

const unsigned short voicable[] = {
   0x82a9, 0x82ab, 0x82ad, 0x82af, 0x82b1, 0x834a, 0x834c, 0x834e, 0x8350,
   0x8352, 0x82b3, 0x82b5, 0x82b7, 0x82b9, 0x82bb, 0x8354, 0x8356, 0x8358,
   0x835a, 0x835c, 0x82bd, 0x82bf, 0x82c2, 0x82c4, 0x82c6, 0x835e, 0x8360,
   0x8363, 0x8365, 0x8367, 0x82cd, 0x82d0, 0x82d3, 0x82d6, 0x82d9, 0x836e,
   0x8371, 0x8374, 0x8377, 0x837a, 0x8345, 0x82a4
};

3-3 #ifdefを使わない

コンパイルスイッチで切り分けてコンパイルする/しない部分を指定するには、 #ifdefを使わず、#ifを使うべきである。

理由を説明すると長くなるが、 たとえば『-DIM_FOO』というスイッチを指定するか省くかによって切り分けている 場合、そのスイッチの名前の綴りを間違えて(『_』を忘れて)、

    if (flag) {
        foo_calc();
#ifdef IMFOO
        foo_adjst();
#endif
    }

のようにしてしまった場合、 名前『IMFOO』は定義されていないため、この部分はコンパイルされない。 (『-DIM_FOO』を附けても)

#ifdefではなく#ifを使って以下のように書いた場合も、

    if (flag) {
        foo_calc();
#if IMFOO
        foo_adjst();
#endif
    }

綴り間違いによって未定義となっている名前の値は『0』と して扱われることがCの文法により定まっているため、やはり同様の問題は起きてしまう。

しかし、たいがいのCコンパイラーでは『未定義の名前を#ifで参照したら 警告を出す』という機能があるため(gccでは-Wundefオプションを指定)、 それを指定することにより、この問題を解決できるのだ。

ところが、この機能を使う場合、#ifdefを使って(かつ、未定義の名前は偽となる ことを期待して書いて)いる箇所があると、それらも一緒に警告され、邪魔になるため、 #ifdefおよび#ifndefは使用すべきではない、というわけだ。

defined()

通常のコンパイルスイッチ的に使う場合ではなく、 以下のように『すでに定義されて』いるかどうかを調べたい場合は、

#ifndef BYTE
#define BYTE unsigned char
#endif

『defined』を用いて下記のように書く。

#if !defined(BYTE)
#define BYTE unsigned char
#endif

同様に#ifndefも使わない

同様の理由で#ifdefも使うべきではないが、それらの理由の他にも、 『#ifdef FOO』と『#ifndef FOO』の視認性の悪さ (この2つはぱっと見て見間違えることが多い)からも、 使うべきではないと言えるだろう。 その点、『#if FOO』と『#if !FOO』ならば、見やすい。


4-1 エンディアン依存のコードを書かない

従来のソースでは以下のような部分がよく見受けられた。

    union {
        BYTE bByte[2];
        WORD wWord;
    } uHiLow;

    uHiLow.bByte[0] = buf[i];
    uHiLow.bByte[1] = buf[i + 1];

    r = func((BYTE *)&uHiLow.wWord)

しかし、これはWORD(unsigned short)が、リトルエンディアン であることが前提のコードであり、ビッグエンディアンの環境では正しく作動しない。

通常のEUCやShiftJISの文字列中のように2バイトのコードが上位バイトが 先に出てくるものを、コードの数値としてintやshortに格納するには、 以下のようにすればよい。

    char buf[MAX];
    int code;

    code = (buf[i] << 8) & 0xff00;
    code |= (buf[i + 1] & 0xff);

この書き方のほうが、汎用性がある(intやshortがリトルエンディアンかビッグエンディアンかに依存しない)だけでなく、 実は効率もよい。(以下参照)

[unionを使ったリトルエンディアン依存の書き方]
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movb    $0, uHiLow+1
    movb    $0, uHiLow
[普通にシフトして代入]
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movl    $0, w