ページ 1 / 1
関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 15:16
by Mokutsuno
いつもお世話になっています。関数ポインタ、コールバック関数について
学習しているのですが、質問です。以下のコードで、
コード:
#include<stdio.h>
typedef void (* FUNC_POINTER)(char *); //関数ポインタのtypedef
void func1(char *s){
printf("%s\n",s);
}
void func2(char *s){
int i;
for(i=0;i<2;i++){
printf("%s\n",s);
}
}
void func(char *s,FUNC_POINTER p){
p(s);
}
int main(){
FUNC_POINTER p;
p = func1;
func("ありがと",p);
p = func2;
func("Thanks",p);
return 0;
}
ポインタ変数pはアドレス、*pは実体と覚えていたので、
p(s);やprintf("%s\n",s);のp、sはポインタであり、アドレスを示していると
思い、実行結果はアドレスがそれぞれ表示されると思ったのですが、
実際は、
ありがと
Thanks
Thanks
と、表示されました。
なので、ここでは実体(pはfunc1、2,sは"ありがと","Thanks")として
使われているように思えるのですが、なぜなのでしょうか?
(printf("%s\n",s);をprintf("%s\n",*s);としてみると、例外が発生しました)
pのほうは、アドレスが"func1"であるため、アドレスがそのまま関数名なので、p(s)はfunc1(1)、func(2)である、ということなのでしょうか?
自分が勘違いしているか、
なにか別の使い方があるのでしょうか?
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 15:55
by みけCAT
言語が指定されていないようなので、とりあえずC言語の話であると仮定します。
Mokutsuno さんが書きました:
pのほうは、アドレスが"func1"であるため、アドレスがそのまま関数名なので、p(s)はfunc1(1)、func(2)である、ということなのでしょうか?
アドレスは関数名ではないし、sは(少なくとも普通の環境では)1や2では無いので、p(s)はfunc1(1)、func(2)ではないです。
まず、関数は(アドレスを求める&演算子やsizeof演算子のオペランドなどの例外を除いて)式の中で使われた場合、その関数を指すポインタに変換されます。
N1570 6.3.2.1 Lvalues, arrays, and function designatorsより引用
4 A function designator is an expression that has function type. Except when it is the
operand of the sizeof operator, the _Alignof operator, 65) or the unary & operator, a
function designator with type ‘‘function returning type’’ is converted to an expression that
has type ‘‘pointer to function returning type’’.
関数func1やfunc2から変換されたポインタがpに代入され、funcに渡されるので、funcではそのポインタを用いて関数を呼び出すことができます。
なお、関数名は通常実行時には(デバッグ用の情報などを除いて)用いられないでしょう。
Mokutsuno さんが書きました:
なので、ここでは実体(pはfunc1、2,sは"ありがと","Thanks")として
使われているように思えるのですが、なぜなのでしょうか?
関数呼び出し演算子やprintf関数が渡されたポインタをデリファレンスし、「実体」を用いた処理を行っているからでしょう。
Mokutsuno さんが書きました:
(printf("%s\n",s);をprintf("%s\n",*s);としてみると、例外が発生しました)
フォーマット%sにはchar*型のデータを渡さないといけません。
char型のデータである*sを渡すと型が違うので
未定義動作になり、
Mokutsunoさんの環境では
たまたま例外が発生するという結果になったのでしょう。
そもそも、*s、すなわちUTF-8なら0xE3や0x54 (実体)だけを渡されても、
%sで出力するべき文字列がどこにあるか(ポインタ)がわからず、困ってしまうでしょう。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 15:58
by かずま
Mokutsuno さんが書きました:
なので、ここでは実体(pはfunc1、2,sは"ありがと","Thanks")として
使われているように思えるのですが、なぜなのでしょうか?
printf("%p\n", s); だとポインタ s の値、すなわちアドレスが表示されます。
"%s" は、そのアドレスから始まる文字列を表示する変換指定です
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 16:06
by みけCAT
ちなみに、どうせ式中の関数は(一部の例外を除いて)自動的にその関数を指すポインタに変換されるので、
コード:
void func(char *s,FUNC_POINTER p){
p(s);
}
は
コード:
void func(char *s,FUNC_POINTER p){
(*p)(s);
}
とも
コード:
void func(char *s,FUNC_POINTER p){
(******************************p)(s);
}
とも書けます。
かずま さんが書きました:printf("%p\n", s); だとポインタ s の値、すなわちアドレスが表示されます。
printf("%p\n", s);は未定義動作になります。
%pに渡すべきデータはvoid*なので、
コード:
printf("%p\n", (void*)s);
とするのが安全でしょう。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 16:41
by Mokutsuno
回答ありがとうございます。
アドレスは関数名ではないということでしたか。
p(S);は間接参照というものを行っていたのですね。
(*p)(s);のように書くことができるのですね。こちらのほうが直感的に
わかりやすいのでこちらを使うことにします。
そして、%sは文字列を埋め込むもの、と思っていましたが、"%s" は、そのアドレスから始まる文字列を表示する変換指定なのですね。
実体を渡すのは間違っていました。
すっきりしました。ありがとうございました。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 16:44
by C6b14
この 形 は C# 、VB では よく使う デリゲート で ラムダ として 実装 されるものと 同じですね。
C では 関数ポインター と呼ばれる と記憶しています。 多分 Windos Form の プログラム では イベント・ハンドラー として実装されるので きずかず に 普通 に使ったことがあるはずです。
まず typedef void (* FUNC_POINTER)(char *); は (引数 が 同じなら スタックメモリー の形が同じ なので)
FUNC_POINTER は すべての char * の 引数を もつ 関数の デリゲート(代理人) に なれる。
と いう事です。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 16:51
by C6b14
アドレスは関数名 なんですけど 前提条件 があります。 と言えば いいのでしょうか。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 16:53
by かずま
みけCAT さんが書きました:
かずま さんが書きました:printf("%p\n", s); だとポインタ s の値、すなわちアドレスが表示されます。
printf("%p\n", s);は未定義動作になります。
未定義動作にはならないでしょう。
N1570 6.2.5 Types より引用
28 A pointer to void shall have the same representation
and alignment requirements as a pointer to a character type.
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 17:28
by かずま
かずま さんが書きました:
N1570 6.2.5 Types より引用
28 A pointer to void shall have the same representation
and alignment requirements as a pointer to a character type.
パラグラフ番号は、28 ではなく 26 でした。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 17:31
by かずま
かずま さんが書きました:パラグラフ番号は、28 ではなく 26 でした。
何度もすみません。
C99 では 26。C11 では 28 です。
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 17:37
by C6b14
質問 は こうゆう 事では ありません?。
ありがと
アドレス=18085732
アドレス=19403632
Thanks
Thanks
アドレス=18085732
アドレス=19403627
コード:
#include<stdio.h>
typedef void(*FUNC_POINTER)(char *); //関数ポインタのtypedef
void func1(char *s) {
printf("%s\n", s);
}
void func2(char *s) {
int i;
for (i = 0; i<2; i++) {
printf("%s\n", s);
}
}
void func(char *s, FUNC_POINTER p) {
p(s);
}
int main() {
FUNC_POINTER p;
p = func1;
func("ありがと", p);
printf("アドレス=%d\n\n", (int)&p);
printf("アドレス=%d\n\n", (int)*p);
p = func2;
func("Thanks", p);
printf("アドレス=%d\n\n", (int)&p);
printf("アドレス=%d\n\n", (int)*p);
return 0;
}
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月27日(日) 18:35
by C6b14
もちろん s は 文字列の先頭アドレス です。 C は 値が 0x00(null文字) が終端なので 先頭さえ決まれば いいです。
バイナリエディタでみると よくわかります。
コード:
printf("アドレス=%d\n\n", (int)&s);
printf("アドレス=%d\n\n", (int)*s);
Re: 関数ポインタのアドレスと実体についてわからないことがあります
Posted: 2016年11月28日(月) 09:45
by C6b14
https://en.wikipedia.org/wiki/Type_signature#C.23
に
コード:
char c;
double d;
int retVal = (*fPtr)(c, d);
の シグネィチャー は
コード:
(int) (char, double);
と あるので 関数ポインター は 引数 だけでなく 戻り値 の 型 も 考慮して 決める ようです。
(なぜ C#, JAVA は 考慮しないでいいのか という 疑問が わきました )