関数ポインタのアドレスと実体についてわからないことがあります

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Mokutsuno
記事: 26
登録日時: 8年前

関数ポインタのアドレスと実体についてわからないことがあります

#1

投稿記事 by Mokutsuno » 8年前

いつもお世話になっています。関数ポインタ、コールバック関数について
学習しているのですが、質問です。以下のコードで、

コード:

#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)である、ということなのでしょうか?

自分が勘違いしているか、
なにか別の使い方があるのでしょうか?

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#2

投稿記事 by みけCAT » 8年前

言語が指定されていないようなので、とりあえず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で出力するべき文字列がどこにあるか(ポインタ)がわからず、困ってしまうでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

かずま

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#3

投稿記事 by かずま » 8年前

Mokutsuno さんが書きました: なので、ここでは実体(pはfunc1、2,sは"ありがと","Thanks")として
使われているように思えるのですが、なぜなのでしょうか?
printf("%p\n", s); だとポインタ s の値、すなわちアドレスが表示されます。
"%s" は、そのアドレスから始まる文字列を表示する変換指定です

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#4

投稿記事 by みけCAT » 8年前

ちなみに、どうせ式中の関数は(一部の例外を除いて)自動的にその関数を指すポインタに変換されるので、

コード:

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);
とするのが安全でしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Mokutsuno
記事: 26
登録日時: 8年前

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#5

投稿記事 by Mokutsuno » 8年前

回答ありがとうございます。
アドレスは関数名ではないということでしたか。
p(S);は間接参照というものを行っていたのですね。
(*p)(s);のように書くことができるのですね。こちらのほうが直感的に
わかりやすいのでこちらを使うことにします。
そして、%sは文字列を埋め込むもの、と思っていましたが、"%s" は、そのアドレスから始まる文字列を表示する変換指定なのですね。
実体を渡すのは間違っていました。
すっきりしました。ありがとうございました。

C6b14

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#6

投稿記事 by C6b14 » 8年前

この 形 は C# 、VB では よく使う デリゲート で  ラムダ として 実装 されるものと 同じですね。
C では 関数ポインター と呼ばれる と記憶しています。 多分 Windos Form の プログラム では イベント・ハンドラー として実装されるので きずかず に 普通 に使ったことがあるはずです。
まず typedef void (* FUNC_POINTER)(char *);  は (引数 が 同じなら スタックメモリー の形が同じ なので)
FUNC_POINTER は すべての  char * の 引数を もつ 関数の デリゲート(代理人) に なれる。

と いう事です。  

C6b14

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#7

投稿記事 by C6b14 » 8年前

アドレスは関数名 なんですけど 前提条件 があります。 と言えば いいのでしょうか。

かずま

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#8

投稿記事 by かずま » 8年前

みけ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: 関数ポインタのアドレスと実体についてわからないことがあります

#9

投稿記事 by かずま » 8年前

かずま さんが書きました: 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: 関数ポインタのアドレスと実体についてわからないことがあります

#10

投稿記事 by かずま » 8年前

かずま さんが書きました:パラグラフ番号は、28 ではなく 26 でした。
何度もすみません。
C99 では 26。C11 では 28 です。

C6b14

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#11

投稿記事 by C6b14 » 8年前

質問 は こうゆう 事では ありません?。

ありがと
アドレス=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;
}

C6b14

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#12

投稿記事 by C6b14 » 8年前

もちろん s は 文字列の先頭アドレス です。 C は 値が 0x00(null文字) が終端なので 先頭さえ決まれば いいです。
バイナリエディタでみると よくわかります。

コード:

	printf("アドレス=%d\n\n", (int)&s);
	printf("アドレス=%d\n\n", (int)*s);

C6b14

Re: 関数ポインタのアドレスと実体についてわからないことがあります

#13

投稿記事 by C6b14 » 8年前

https://en.wikipedia.org/wiki/Type_signature#C.23

コード:

char c;
double d;
int retVal = (*fPtr)(c, d);
の シグネィチャー は

コード:

(int) (char, double);
と あるので 関数ポインター は 引数 だけでなく 戻り値 の 型 も 考慮して 決める ようです。

(なぜ C#, JAVA は 考慮しないでいいのか という 疑問が わきました ) 

閉鎖

“C言語何でも質問掲示板” へ戻る