Cにおけるヘッダ・ファイルの話 [CodeWarrior]
前回、Cにおける外部参照の話では、プロトタイプ宣言に一貫性が無い場合に問題が起こることを示しました。 今回は、一貫性を保証するためにどんな方法があるかを考えます。
プロトタイプ宣言を等しくするヘッダ・ファイル
答えを先に書きます。 「ヘッダ・ファイルの中にプロトタイプ宣言を書いて、宣言が必要な全てのソース・コードは、ヘッダ・ファイルを呼び出す。」 という約束にしてしまうのです。 この場合のヘッダ・ファイルは、このようになります。
/* sub.h */ word sub1(byte p1, word p2); /* prototype */
そして、 "sub.c" ファイルと "main.c" ファイルからは、プロトタイプを宣言する代わりにヘッダ・ファイルを #include します。
/* sub.c */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include "sub.h" word sub1(byte p1, word p2) { MTIMMOD = p1; TPMC1V = p2; return MTIMCNT; }
/* main.c */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include "sub.h" volatile word value; void main(void) { value = sub1(50, 1200); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
新たに作成したヘッダ・ファイルは、 "Sources" フォルダに入れます。 しかし、プロジェクト・ペインでは "Sources" には入れずに "Includes" に入れます。
これは、 "sub.h" を単体でコンパイルさせないための工夫です。 ただ、この工夫が本当に正しく CodeWarrior に理解されているのかは、今のところ不明です。
約束は、破られるためにある?!
この「約束」は、あくまでも紳士協定なので、従わないプログラムを書こうと思えばいくらでも書けます。 以前、USBプロジェクト - サンプル・ソフトへの長い道のり(後編のつもりだった)で紹介した "LPGUID" の定義などは、その一例です。 複数のヘッダ・ファイルに同じ型宣言が散逸していて、その場しのぎの対応をするというのは、バグの温床に他なりません。
バグを早期発見する方法は、バグを早期発見できる「約束」を作り、それを「守る」ことです。
もし、関数定義を間違えていたら
ヘッダ・ファイルを使用する時の効果は、コンパイル時に誤りを見つけやすいということです。 そこで、関数定義部分で間違えた場合に何が起こるか調べてみました。
/* sub.c */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include "sub.h" word sub1(word p2, byte p1) { MTIMMOD = p1; TPMC1V = p2; return MTIMCNT; }
例として、引数の順番を間違えてしまった場合を考えます。 この時には、以下のようなエラーが発生します。
Warning : C1140: This function is already declared and has a different prototype sub.c line 7 Warning : C1020: Incompatible type to previous declaration (found 'unsigned int (*) (unsigned int ,unsigned char )', expected 'unsigned int (*) (unsigned char ,unsigned int )') sub.c line 7 C1440: This is causing previous message 1020 sub.h line 3
見事に、 sub.h ファイル内のプロトタイプ宣言と実際の関数定義が異なっていると指摘してくれました。 決して、 Warning だからと甘く見てはいけません。
もし、関数の参照のしかたを間違えていたら
次は、関数を参照する部分、具体的には、 main.c ファイルで間違えたらどうなるかを確認してみます。
/* main.c */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include "sub.h" volatile word value; void main(void) { value = sub1(50, 1200, 10000); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
最初は、引数の数を間違えた場合です。 この場合には、以下のエラーが発生します。
Error : C1821: Wrong number of arguments main.c line 10 Error : Compile failed
これは、前回、Cにおける外部参照の話で見た例と同じですね。
次は、引数の順番を間違えた場合のソース・コードと結果です。
/* main.c */ #include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include "sub.h" volatile word value; void main(void) { value = sub1(1200, 50); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
Warning : C2705: Possible loss of data main.c line 10
これも、 Warning だからといって、見逃してはいけません。 16ビットの値 "1200" を8ビットの引数 "p1" に入れようとしている事をおかしいと判断している、真性の Warning です。
もし、プロトタイプ宣言を間違えていたら
プロトタイプ宣言は、つまり「仕様」なのだから、コーディング後に書き換えられるのは、筋違いだ。 もっともなご意見ですが、開発初期には、「仕様」が途中で変わってしまう場合もあります。 えっ? 出荷直前であっても「仕様」を変えられてしまうのですか? ご愁傷様です。
/* sub.h */ word sub1(byte p1, word p2, word p3); /* prototype */
Error : C1821: Wrong number of arguments main.c line 10 Error : Compile failed Warning : C1140: This function is already declared and has a different prototype sub.c line 7 Warning : C1020: Incompatible type to previous declaration (found 'unsigned int (*) (unsigned char ,unsigned int )', expected 'unsigned int (*) (unsigned char ,unsigned int ,unsigned int )') sub.c line 7 C1440: This is causing previous message 1020 sub.h line 3
引数の数が違っている場合には、 main.c と sub.c の双方のコンパイルで堂々としたエラーが発生します。
/* sub.h */ word sub1(word p2, byte p1); /* prototype */
Warning : C2705: Possible loss of data main.c line 10 Warning : C1140: This function is already declared and has a different prototype sub.c line 7 Warning : C1020: Incompatible type to previous declaration (found 'unsigned int (*) (unsigned char ,unsigned int )', expected 'unsigned int (*) (unsigned int ,unsigned char )') sub.c line 7 C1440: This is causing previous message 1020 sub.h line 3
引数の順番を間違えた場合にも、双方のコンパイルで Warning が発生します。 ただし、この場合には、 Warning なので、コンパイルとリンクそのものは終了して機械語の出力が得られます。 せっかく、コンパイラが Warning を出してくれたのですから、どこに問題があるのか、しっかりと確認しましょう。
/* sub.h */ word sub2(byte p1, word p2); /* prototype */
Warning : C1801: Implicit parameter-declaration for 'sub1' main.c line 10
この例は、プロトタイプ宣言で関数の名前を間違えてしまった場合です。 そんな、馬鹿な間違いはしないとお思いでしょうが、意外にソース・コードを見ただけでは問題を見つけられないという場面に出会います。 次回は、「ソース・コードを一見しただけでは発見できない」誤りをご紹介します。
HC08JB8でおもちゃを作りたいです。
PICなどはライブラリーがあって、
そこからある程度持ってくれば出来るのですが、
HC08JB8ではとんと勝手が分かりません。
したいことは「USB」から「I2C」と「1bit」信号で外部機器を動かしたいのです。
集めたのは それなりのハード、USBDMJS16、外部機器のコマンド表、
USBのVID、PIDなどと、外部機器のAPIソフトです。
by nekonoko (2010-04-18 14:39)