Cにおける外部参照の話 [CodeWarrior]
前方参照のお話では、ソースコードを単一のファイルに記述した場合を考えました。 現実のアプリケーション開発では、複数のファイルに関数を記述して、参照する場合が多く有ります。 このとき、どんな点に注意すれば良いのでしょうか。
関数だけくくり出すと
ここでは、 main.c と sub.c という二つのファイルが有ると想定し、 sub.c で宣言された sub1 関数を main.c から呼び出す場合を考えてみます。 まずは、 sub1 関数だけを くくり出して sub.c ファイルに移動します。
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ word sub1(byte p1, word p2) { MTIMMOD = p1; TPMC1V = p2; return MTIMCNT; }
main.c にあったものと同じ #include が追加されていますが、これは sub1 関数で使用されているレジスタ名を宣言させるためのものです。
このファイルを単独でコンパイルすると正常にコンパイルされます。 sub.c ファイルをコンパイル対象とするためには、このファイルを Sources フォルダに入れる必要がありますのでお忘れなく。
一方の main.c ファイルからは、 sub1 関数の本体部分だけを削除し、プロトタイプは残します。
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ word sub1(byte p1, word p2); /* prototype */ volatile word value; void main(void) { value = sub1(50, 1200); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
これだけで、コンパイルとリンクがエラーなく行われます。 シミュレータで確認しても、 $E09F 番地の sub1 という関数を呼び出していることがわかります。
extern は、不要なのか?
外部関数を呼び出す際には、多くの場合 extern という修飾子を付けますが、付けなくてもエラーにはなりませんでした。 え~と、これは何故なんでしょうか?
extern というのは、記憶クラスと呼ばれるものの一つです。 記憶クラスには、他に auto static というものがあります。 このうち、 auto は、関数定義の内部でのみ使われる記憶クラスです。 試しに main.c の sub1 関数宣言に auto 記憶クラスをつけて「auto word sub1(byte p1, word p2); /* prototype */」とするとエラーになりました。
Error : C1005: Illegal storage class main.c line 4
ただ、 main 関数の中で auto 記憶クラスの関数を定義してもエラーにはなりませんでした。
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ volatile word value; void main(void) { auto word sub1(byte p1, word p2); /* prototype */ value = sub1(50, 1200); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
このことから、関数の宣言も変数の宣言と同じように、関数の外部であれば、 extern (外部からの参照が可能)がデフォルトになり、関数の内部であれば、 auto (関数の内部でだけ有効)がデフォルトになると考えられます。 つまり、 extern をわざわざ付けてやる必要なんかなかったということになります。
ちなみに、 static を付けた場合には、「外部からの参照が不可能」という意味になります。 そのため、 main.c ファイルの内部で sub1 関数を定義する必要があります。 この場合には、エラーが発生します。
Warning : C3603: Static 'sub1' was not defined main.c line 4
sub1 関数が、 main.c ファイル内部で定義されていないという意味のエラーです。
引数の数を間違えたら、どうなるか
main.c ファイルの中では、プロトタイプ宣言と実際の使い方に矛盾が無かったため、コンパイルエラーは起きません。 それでは、どちらも間違えていた場合には、何が起こるのでしょうか。
#include <hidef.h> /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ word sub1(byte p1, word p2, word p3); /* prototype */ volatile word value; void main(void) { value = sub1(50, 1200, 10000); for(;;) { } /* loop forever */ /* please make sure that you never leave main */ }
コンパイルすると、エラーは発生しません。 コンパイル単位である main.c ファイル内部で矛盾がないのだから当然です。 リンクする際にもエラーは発生しません。 リンクするときに引数のチェックは行われないので、これも当然です。
そこで、 main.c ファイルを Dsiassemble して、どんな機械語に変換されたかを確認してみました。
9: value = sub1(50, 1200, 10000); 0000 a632 [2] LDA #50 0002 87 [2] PSHA 0003 4504b0 [3] LDHX #1200 0006 89 [2] PSHX 0007 8b [2] PSHH 0008 452710 [3] LDHX #10000 000b cd0000 [6] JSR sub1 000e a703 [2] AIS #3 0010 960000 [5] STHX value
アセンブラが読めない人、ごめんなさい。 かいつまんで説明すると、三つの引数は、それぞれ、以下のように割り当てられて sub1 関数に引き渡されています。
引数名 | 値 | 格納場所 |
---|---|---|
p1 | 50 | Aレジスタ |
p2 | 1200 | スタック内 |
p3 | 10000 | HXレジスタ |
一方、 sub1 関数は、このようにコンパイルされています。
5: MTIMMOD = p1; 0000 b700 [3] STA _MTIMMOD 6: TPMC1V = p2; 0002 3500 [5] STHX _TPMC1V 7: return MTIMCNT; 0004 be00 [3] LDX _MTIMCNT 0006 8c [1] CLRH 8: } 0007 81 [6] RTS
これも、解説すると、こうなっています。
引数名 | 値 | 格納場所 |
---|---|---|
p1 | 50 | Aレジスタ |
p2 | 10000 | HXレジスタ |
つまり、二番目の引数"1200"は、 p2 には引き渡されず、三番目の引数"10000"が p2 に引き渡されてしまっています。 コンパイラが、プログラムを記述した人の意思と異なったコンパイルを行っていて、それがコンパイル時もリンク時もエラーとして報告されない。 これは、ゆゆしき事態ではありませんか。
この原因は、 sub.c ファイルの関数定義と main.c のプロトタイプ宣言が異なっているからにほかなりません。 どうやって、双方のプロトタイプ宣言が等しくなるように保証してやるか。 これは、次回のテーマとします。
CodeWarrior、久しく触ってないな〜。
昔は、Mac(OS9とOSXのCarbon環境で)や、V850なんかでCodeWarriorを使ってたんですが。
また触ってみたい。
by Ponta (2009-08-15 23:30)