- ベストアンサー
関数のアドレスをコピーすることについて
◎1--------------------------- #include<stdio.h> void (*putdata)(int d); //// (1) //// void english(int dt); void japanese(int dt); int main( ) { int a=234; putdata=english; putdata(a); putdata=japanese; putdata(a); return 0; } void english(int dt) { printf("Value is %d.\n",dt); } void japanese(int dt) { printf("数値は%dです.\n",dt); } --------------------------------- ◎2-------------------------------- #include<stdio.h> int (*get_sign) ( ); //// (2) //// int iget_sign(int *a); int dget_sign(double *a); int main( ) { int sgn,idt=6; double fdt=-2.123; get_sign=iget_sign; sgn=get_sign(&idt); printf("data:%d sgn:%d\n",idt,sgn); get_sign=dget_sign; sgn=get_sign(&fdt); printf("data:%f sgn:%d\n",fdt,sgn); return 0; } int iget_sign(int *a) { if(*a==0) return 0; else if(*a>0) return 1; else return -1; } int dget_sign(double *a) { if(*a==0.0) return 0; else if(*a>0.0) return 1; else return -1; } --------------------------------- 以上2つのプログラムについて疑問があります。 ◎1の「putdata=english;」と関数のアドレスをコピーするにおいて、(1)の部分の引数の「(int d)」がどういう意味があるか分かりません。 次に◎2の(2)の部分で、「int (*get_sign) ( );」と引数を空にするというのが理解できません。本当は(2)の部分を、「int (*get_sign)(void *a);」 にしなくてはいけないと参考書に書いてあったのですが、何故そうしなくてはいけないかも理解できません。 C++で実行すると、チェックが厳しいということで、エラーが出てしまうというのは、参考書には書いてあったのですが、その前に(1)、(2)での関数プロトタイプの引数の表現方法が理解できません。 教えていただけると嬉しいです。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
int foo (); という関数の宣言があった場合, C : fooはint型の値を返す関数 C++ : fooは「引数をとらず」int型の値を返す関数 と,関数宣言の引数として何も書かなかった時の意味合いが異なってきます。 また,Cでは任意の関数へのポインタ型の値を任意の関数へのポインタ型に変換できますが, C++ではこの変換は存在しません。 # キャストと変換は異なります。 このため,C++ではそもそもコンパイル時にエラーとなります。
その他の回答 (6)
- Yune-Kichi
- ベストアンサー率74% (465/626)
> 本当は(2)の部分を、「int (*get_sign)(void *a);」 > にしなくてはいけないと参考書に書いてあったのですが、何故そうしなくてはいけないかも理解できません。 ここは参考書が間違っています。 なので,理解する必要もないです。 以下,国際標準規格ISO/IEC 9899:1999 Programming languages - C (ISと略します) を元に「間違っていること」の説明をします。 int *やdouble *とvoid *はcompatibleな型ではないため,int (*)(void *)とint (*)(int *)はcompatibleな型ではありません。 # IS 6.2.7 Compatible type and composite type - Paragraph 3 / IS 6.7.5.1 Pointer declarators - Paragraph 2 このため,get_signにiget_signを代入してget_signに関数呼び出し演算子を適用した結果は,未定義動作となります。 # IS 6.5.2.2 Function calls - Paragraph 9 / IS 6.3 Conversions - 6.3.2.3 Pointers - Paragraph 8 それに対して,片方のみにparameter type listがある状態になる,int (*)()とint (*)(int *)はcompatibleな型になります。 # IS 6.2.7 Compatible type and composite type - Paragraph 1, 3 このため,「int (*get_sign)(void *a)」と「してはならない」のが正しいです。 # 未定義動作とは,動作の結果がどうなるかわからないものです。「鼻から悪魔」などと形容されたりします。 ちなみに,ポインタの値の表現は,元となる型によって異なってもかまいません。 # IS 6.2.6 Representations of types - 6.2.6.1 General - Paragraph 1 それなのになぜvoid *が許されるかというと,void *への変換またはvoid *からの変換であることをコンパイラが知っているからです。 void *へのorからの変換については,その規定があるために,コンパイラはそれが正しく動作するように実装することが求められます。 ただし,void *へのorからの変換についても,関数へのポインタについては求められないことになっています。 # IS 6.3 Conversions - 6.3.2.3 Pointers - Paragraph 1
お礼
ご回答ありがとうございます。 C初心者なもので、詳しくは理解できませんでした。すいません。 ◎2について、 (2)の部分を「int (*get_sign) ( );」や「int (*get_sign) (void *a);」としCとして実行したら実行できましたが、C++では出来ませんでした。 これは何故なのでしょうか?
- vipasigaru
- ベストアンサー率38% (16/42)
No4の回答のお礼への回答 ・void*について > void型という変数*aと認識したのですが、 まちがっています。 void型の変数*aではなく、void*型の変数aです。 void*型は、「型は未定な変数の先頭アドレス」を指します。 int*型は、「型がintな変数の先頭アドレス」を double*型は、「型がdoubleな変数の先頭アドレス」をそれぞれ指します。 void* aを(int*)aとすると「型がintな変数の先頭アドレス」、 void* aを(double*)aとすると「型がdoubleな変数の先頭アドレス」 と解釈することができて、それぞれの変数にアクセスできるというわけです。 ・void (*putdata)( );について int sub()とint sub(void)は異なります。 引数に明示的にvoid型を宣言していない関数は、「可変引数の関数」ということになります。 void型を宣言すると、「引数を持たない関数」です。
お礼
ご回答ありがとうございます。 >void型の変数*aではなく、void*型の変数aです。 >void*型は、「型は未定な変数の先頭アドレス」を指します。 >int*型は、「型がintな変数の先頭アドレス」を >double*型は、「型がdoubleな変数の先頭アドレス」をそれぞれ指します。 上記のご回答の先頭アドレスを渡すという考え理解できました。 ありがとうございます。
- Tacosan
- ベストアンサー率23% (3656/15482)
「void 型」の変数を宣言することはできません. 当然, 関数の仮引数でもアウト. あと, void (*putdata)( ); と宣言すると, putdata は「値を返さない関数へのポインタ」となります. 一方 void (*putdata)(int *); では「int * を引数にとり値を返さない関数へのポインタ」です. 関数へのポインタや関数のプロトタイプ宣言において () の中を空にすると「引数に対する情報 (引数の数や個々の引数の型) を一切与えない」ということを意味します. 関数を定義するときには, これとは異なり「空 = 引数を持たない」と解釈されます.
お礼
ご回答ありがとうございます。 >「void 型」の変数を宣言することはできません とご回答してくれましたが、 No.3の回答者様が『何かのポインタ型』という宣言の仕方が『void*』になります。と回答してくれたのですが、「int (*get_sign) (void *a);」はint型にもdouble型にも対応できる、void型という変数*aと認識したのですが、この認識は間違っているのでしょうか? ◎1、2の(1)、(2)「void (*putdata)( );」「int (*get_sign) ( );」のように引数を書かないと2つのプログラムとも実行できたのが、いまいち理解できません。 教えていただけると嬉しいです。
- redfox63
- ベストアンサー率71% (1325/1856)
1)については引数の型をあわせるためです putdataの実際に呼び出されるEnglish/Japaneseは int型の引数を取ります void(*putdata)(); と宣言してしまうと putdata(a); などといった使い方をする場合プロトタイプ宣言した物とは違う物と認識されコンパイルエラーになる可能性が高いでしょう 2)については 引数の型が int*とdouble*の2種類あります この場合 int (*get_sign)(int *); とした場合 get_sign = dget_sign;を実行しようとすると引数の型が違うのでエラーになると思います そこで何かのポインタ型の引数を取る関数ポインタといった宣言方法にしないと面白くありません この『何かのポインタ型』という宣言の仕方が『void*』になります 場合によって実際の型が変わります get_sign = iget_sign; とすれば引数は int* get_sign = dget_sign; とすれば引数は double* といった具合に解釈されます
お礼
ご回答ありがとうございます。 >『何かのポインタ型』という宣言の仕方が『void*』になります >場合によって実際の型が変わります >get_sign = iget_sign; >とすれば引数は int* >get_sign = dget_sign; >とすれば引数は double* 以上のご回答納得できました! ◎1について、◎2のように(1)の部分を「void (*putdata)(void d);」として何かの変数型dとするとエラーが出てしまいました。この表現はだめなのでしょうか? しかし、「void (*putdata)( );」とすると何故か実行できました。 何故なのか教えてもらえると嬉しいです。 後、「void (*putdata)( );」のようの引数を空にすると、引数はvoidと認識されるのでしょうか?
補足
自分でもいろいろと勉強してみました。 例えば、「void *p;」などのvoid型ポインタは宣言できると分かりました。 それで「*(int*)p;」や「*(double*)p;」と他の型にみなす事が出来るという事ですね。 1. ◎2について、「int (*get_sign) ( );」と引数を空にすると引数の型と個数をチェックしないという事で、実行できたのだと自分は認識しました。次に、「int (*get_sign) (void *a);」とすると、void型ポインタという事で実行できたと認識しました。 以上は、Cだから出来たのだと思います。 2. C++の場合「int (*get_sign) ( );」と引数を空にすると( )内はvoidと改められてしまうため、実行できないのかなと思いました。 それならばと、「int (*get_sign) (void *a);」としても「'int (__cdecl *)(int *)' から 'int (__cdecl *)(void *)' に変換できません。」とエラーが出てしまいました。これも( )内がvoidとみなされてしまったのかなと思いました。 1.の考えは合っていますでしょうか? 次の2.の考えは合っていますでしょうか?それならばC++で実行可能にするにはどうすればよいのでしょうか? 教えていただければ嬉しいです。
- notnot
- ベストアンサー率47% (4900/10358)
普通の関数プロトタイプでの引数の型指定については理解されているのでしょうか? それと同じですが。 もしそもそも関数プロトタイプについてわからないのであればもっと簡単なパターンで考えると良いかと。
お礼
ご回答ありがとうございます。 関数プロトタイプについては理解しています。 関数へのポインタの利用でつまづいてしまいました。 関数のアドレスをコピーするということで、アドレスが代入される関数プロトタイプの引数の表現の仕方がちょっと理解できていない状態です。
- asuncion
- ベストアンサー率33% (2127/6289)
>◎1の「putdata=english;」と関数のアドレスをコピーするにおいて、(1)の部分の引数の「(int d)」がどういう意味があるか分かりません。 (1)の部分は、「putdataは、int型の引数を取り、戻り値がない関数へのポインターである」と読みます。 つまり、english関数やjapanese関数のような、 「int型の引数を取り、戻り値がない関数」を代表しています。 よって、 >putdata=english; >putdata(a); この2行で english(a); と同じ意味を持ちます。
お礼
>(1)の部分は、「putdataは、int型の引数を取り、戻り値がない関数へのポ >インターである」と読みます。 >つまり、english関数やjapanese関数のような、 >「int型の引数を取り、戻り値がない関数」を代表しています。 int型の引数を取り、戻り値がない関数を代表しているというご回答納得できました! ありがとうございます。
お礼
ご回答ありがとうございます。 >Cでは任意の関数へのポインタ型の値を任意の関数へのポインタ型に変換で >きますが, >C++ではこの変換は存在しません。 ># キャストと変換は異なります。 上記の内容とても納得できました! 確かにC++でvoid型ポインタによるキャスト変換は出来ました。 「int foo( );」と引数を空にすると、C++では引数を勝手にvoidとしてしまうから実行出来ないという認識は合っているでしょうか?