• ベストアンサー

プロトタイプ宣言について

C言語で関数を作成しプロトタイプ宣言するときの質問です。 関数実体の引数に構造体のポインタを宣言します。 プロトタイプ宣言には,構造体のポインタを宣言したのと同じ位置にvoid *を宣言します。 関数実体とプロトタイプ宣言は,異なるファイルです。 このように作成した関数は,VC++2008では,コンパイルできるのですが, なぜ関数実体の宣言とプロトタイプ宣言の型が異なるのにコンパイルできるのでしょうか? また,この関数を別ファイルの関数から呼び出した場合,正しく引数を理解し,正しく処理されます。 これは,言語仕様として正しい書き方なのでしょうか? それとも環境依存の書き方なのでしょうか? ご存知の方がいましたらお答えお待ちしています。

質問者が選んだベストアンサー

  • ベストアンサー
  • aris-wiz
  • ベストアンサー率38% (96/252)
回答No.8

> 言語仕様として正しい書き方なのでしょうか? 殆どの環境が完全に基準に準拠しているわけではないので、 言語仕様や規格云々をこまめに気にしても仕方ないとは 思いますが、気になるなら規格書くらいは一読する事を お勧めします。 >この関数を別ファイルの関数から呼び出した場合, >正しく引数を理解し,正しく処理されます C言語の規格では 「呼び出される関数を表す式が関数原型を 含む型を持つ場合実引数の個数は仮引数の個数と 一致しなければならなず各実引数は対応する仮引数の型の非修飾版を もつオブジェクトにその値を代入することのできる型を 持たなければならない。」 とされています。 void*であれば如何なる型のポインタ型にも変換でき、 ポインタ型は不完全オブジェクトから派生することができる 非修飾型となる為、関数の仮引数から実引数に渡す事が 可能だからだと思われます。 >それとも環境依存の書き方なのでしょうか? 環境依存に依存すると思います。 しかし、宣言時の関数の型と定義時の関数の型が 異なる為、基本的にはその様な実装は控えるべきでしょう。 そもそも、宣言をvoid*にする意味が全く分かりません。 どうせ、その構造体以外もポインタは処理できないのだから、 構造体のポインタを受け取るべきです。

_proxima_
質問者

お礼

ご回答ありがとうございます。 C言語の規格の解釈からすると可能であるということですね。 但し,コンパイラに依存するためこのような実装は避けるべきということでしょうか。 大変参考になりました。 私もこのそうな実装は避けるべきだと考えております。 今回は,あるライブラリを眺めている最中にこのような記述に出会いましたので,質問させて頂きました。 全く意味がわからないのは同感です。

その他の回答 (9)

  • aris-wiz
  • ベストアンサー率38% (96/252)
回答No.10

ANo.8の訂正です。 × どうせ、その構造体以外もポインタは処理できないのだから、              ↓ ○ どうせ、その構造体以外のポインタは処理できないのだから、

_proxima_
質問者

お礼

訂正ありがとうございます。 ご回答の文意は理解できた考えております。 親切にありがとうございます。

  • asuncion
  • ベストアンサー率33% (2127/6290)
回答No.9

#4への補足で、 > void func( _STRUCT *pst ) > void func( void *p ); このように、関数定義とプロトタイプ宣言とで わざわざ別の書き方をしたのは、何か理由があってのことですか? 普通(私だけかもしれませんが)、どちらかを先に書いて、 他方へはコピペするんじゃないのでしょうか。

_proxima_
質問者

お礼

ご回答ありがとうございます。 今回は,質問内容を具体化したサンプルコードでの話ですので 特に理由はありません。 私もコピーしますよ。

  • php504
  • ベストアンサー率42% (926/2160)
回答No.7

#4の補足を cygwin の gcc(3.4.4) でやった結果です func.c:6: error: conflicting types for 'func' func.h:7: error: previous declaration of 'func' was here でした

_proxima_
質問者

お礼

補足ありがとうございます。 gccの結果を見ると今回の問題は, VC++固有の問題であると考えられますね。 関数実体と同じプロトタイプ宣言をすればよいだけの話ですが, なぜ”void *”でコンパイルできるのか疑問でした。 コードの汎用性を高めるためにも #4の様な書き方は,止めたほうがよいですね。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

本題とは全然関係ないけど, 「_STRUCT」なんて名前を使った時点でどうなってもおかしくないわな. あとは, まあ警告レベルを最高にしておくとか.

_proxima_
質問者

お礼

確かにそうですね。 「_STRUCT」と書いたとき自分でも疑問を持ちました。 例ということで,投稿に踏み切りましたが, やはり,ご指摘されましたね。 ちなみに,警告レベルを最高にして試験を行いましたが, 当該箇所で警告はありませんでした。 ご指摘ありがとうございました。

  • mtaka2
  • ベストアンサー率73% (867/1179)
回答No.5

おそらく、共通のヘッダファイルをincludeするなどの処理は行わずに、 ソース中に埋め込む形で、 関数を利用しているソースの方では、void*なプロトタイプ宣言をしているが、 関数の定義をしているソースの方では、プロトタイプ宣言はしていない、 という利用状況なのでしょうか。 だとしたら、 ・プロトタイプ宣言は、型についての「参考情報」にすぎない  (歴史的事情から、C言語ではプロトタイプ宣言のない関数呼び出しも許されている) ・任意の型へのポインタから、void* にキャストしてから、また元の型にキャストし戻すと、正しく元通りになることが保証されている。 ・実装依存だが、大抵の環境ではポインタの内部表現はどの型でも等しい。 以上の条件から、型が違っていても引数の受け渡しはたまたまうまくいっている、ということになります。 今回の例では関数呼び出し時に構造体へのポインタをvoidへのポインタにキャストしていますが、 関数実体では、voidへのポインタを構造体へのポインタとして取り扱っています。 言語の実装としては、データ上はどの型へのポインタも内部表現は同じで、ポインタのキャストは、ポインタの実データについては何もいじらない場合が多いので、問題が起きていないわけですが、 言語仕様としては、このような処理は未定義です。どんな環境でも正しく動作するという保証はありません。 「コンパイラが問題を検出できる」「正しい」コードにするには、 ・プロトタイプ宣言はすべてヘッダファイルに記述し ・関数を利用するソースからだけでなく、関数を定義しているソースの方でもヘッダファイルをinlucdeする ようにすればよいでしょう。そうすれば、プロトタイプ宣言の型チェックによって、関数の定義と利用での引数の型のずれがチェックできます。 利用側では構造体の定義をしたくない/できない、という場合もあると思いますが、 そういう場合は、構造体の実体を定義せず名前だけ宣言することで、 構造体へのポインタはプロトタイプ宣言に利用できるようになります。 ---ヘッダファイル ここから--- struct hoge; sturct hoge *hoge_alloc(void); void hoge_free(struct hoge *p); int hoge_get(struct hoge *p); void hoge_set(struct hoge *p, int v); ---ヘッダファイル ここまで--- ---関数定義ソース ここから--- #include "ヘッダファイル" struct hoge { int value; }; sturct hoge *hoge_alloc(void) { return malloc(sizeof(struct hoge)); } void hoge_free(struct hoge *p) { free p; } int hoge_get(struct hoge *p) { return p->value; } void hoge_set(struct hoge *p, int v); { p->value = v; } ---関数定義ソース ここまで--- ---関数利用ソース ここから--- #include <stdio.h> #include "ヘッダファイル" void test(void) { struct hoge *ptr = hoge_alloc(); hoge_set(ptr, 10); printf("%d\n", hoge_get(ptr)); hoge_free(ptr); } ---関数利用ソース ここまで--- こんな感じで、関数利用側では「構造体の定義」については未知のままでも、 「構造体へのポインタ」を取り扱うことが出来ます。

_proxima_
質問者

お礼

ご回答ありがとうございます。 サンプルコードを書かなかった私のミスですが, 利用状況の想定が異なっております。 詳しくは#4を見てください。 ご提示頂いたコードですが,構造体宣言の隠蔽化の方法として 大変参考になりました。 また,”型が違っていても引数の受け渡しはたまたまうまくいっている”とありますが,他の回答をみるとその通りではないかと考えています。ただコンパイルできなコンパイラもあるようです。 詳細な内容のご回答,ありがとうございました。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.4

実際にどう書いて、どうコンパイルしたかによります。 ソースコード・ソースファイル名・コンパイルオプションを補足してください。

_proxima_
質問者

補足

ソースコードの例では,以下の様になります。 ---------- func.c ---------- #include <stdio.h> #include "func.h" void func( _STRUCT *pst ) { pst->a = 1; pst->b = 10; pst->c = 100; return; } ---------- func.h ---------- typedef struct { int a; int b; int c; } _STRUCT; void func( void *p ); ---------- main.c ---------- #include <stdio.h> #include "func.h" int main( int argc, char *argv[] ) { _STRUCT st; func( &st ); printf( "%d, %d, %d\n", st.a, st.b, st.c ); return 0; } コンパイルオプションは, ”Cコードとしてコンパイル(/TC)”を設定した程度で 後は,初期値です。

  • tadys
  • ベストアンサー率40% (856/2135)
回答No.3

C言語では同じ関数名で引数の型や個数が違うものを定義しようとするとエラーになりますが C++言語では関数名が同じでも引数の型や個数が違うものは別の関数として扱われますのでエラーになりません。 これを関数の多重定義と呼んでいます。 http://homepage2.nifty.com/aito/cpp/node3.html

_proxima_
質問者

お礼

ご回答ありがとうございます。 今回は,C言語を対象にしておりますので C++多重定義は考慮されないと考えております。 ヒントとしてご回答くださったと思いますので 重ねてお礼申し上げます。

  • php504
  • ベストアンサー率42% (926/2160)
回答No.2

borlandのBCCとgccではちゃんとエラーになりましたのでVC++の独自仕様でしょうか

_proxima_
質問者

お礼

ご回答ありがとうございます。 今回の質問では,汎用性があるコードかそうでないかが 最大の疑問でした。 当方は,VC++しか知識が無く, 他のコンパイラでの状況を教えていただき感謝しています。 ありがとうございました。

  • redfox63
  • ベストアンサー率71% (1325/1856)
回答No.1

関数実体の記述が 関数を使用する部位より以前に記述してあるのではないでしょうか void foo( struct _tagTest * pStruct) {   pStruct->nID = 1; } void bar() {   struct _tagTest objTS;   foo( &obj ); } といった具合ならエラーになりません void bar() {   struct _tagTest objTS;   foo( &obj ); } void foo( struct _tagTest * pStruct) {   pStruct->nID = 1; } とした場合『foo( &obj );』の行を foo( void* )だと解釈してリンクエラーになるでしょう 前者の記述をした際に bar内で foo( と記述した際に出る ポップアップヒントが 『1/2 foo( void* )』といった具合になっていると思いますよ foo( void* )と for( struct tagTest* pStruct )は別物だと解釈します

_proxima_
質問者

お礼

ご回答ありがとうございます。 確かに関数の順序によって,解釈のされかたが変わり リンクエラーとなることが予想されますね。 サンプルコードを記載しなかった私のミスですが, 今回は,ご回答いただいた状況と異なる状況だと考えております。 複数ファイルに展開されたコードとプロトタイプ宣言とが 絡み合った問題ではないかと考えています。 丁寧な内容のご回答,ありがとうございました。