質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1954閲覧

関数へのポインタを返す関数

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2017/08/05 08:29

編集2017/08/05 13:08

正常に動作する関数を載せます。

int funcs(int a, int b) { printf("%d", a + b); return a + b; } int (*se(int b))(int , int) { printf("%d\n", b); return funcs; } int main(){ int (*sp)() = se(100); int i = sp(2,3); return 0; }

次の実験コードです。
返り値がない場合とある場合で対処できるようにします。

void (*sell(void))(int, int) { return funcs; } int (*pp)() = (int(*)(int, int))sell(); // int (*pp)() = (int(*(void))(int, int))sell(); errorです。 int k = pp(1, 1);

これは、エラーやwarningは出現しませんが、funcs()内のprintfが表示されません。
主にキャストの場所とやり方だと思いますが、どこがいけないのでしょうか?

関数のポインタを返すための関数では、1つ型の関数しか返せないんでしょうか??

次はパラメータの数が異なる関数の場合です。

int func(int a, int b, int c) { printf("%d", a + b + c); return a + b + c; } void (*sell(int c))(int, int) { if(c == 0) return func; else return funcs; }

func()の方はパラメータが3つあります。
パラメータの数が異なる関数をreturnさせたい場合、どのように記述すれば良いのでしょうか?

つまり、
異なる型(返り値がある・ない、パラメータの数が異なる・パラメータなし)の関数のポインタをreturn できるような関数を記述したいです。

どなたか教えて下さい。

実験環境は、Windows 64bit visual studio cl オプションなし です。

「追記」___________________________
Saitoさんの回答とコメントを元に作ってみました。
こんな感じですかね・・・・

C

1#include<stdio.h> 2 3int func(int a, int b) { 4 return a + b; 5} 6 7void funcs(void) { 8 printf("Hello in funcs\n"); 9} 10 11int (*set(int c))() { 12 if (c) 13 return func; 14 else 15 return (int (*)(void))funcs; //一度キャストしておく 16} 17 18int main() { 19 void (*p)() = (void (*)(void))set(0); // キャストして元に戻す 20 int (*r)() = set(1); 21 22 p(); 23 printf("%d\n", r(1,40)); 24 return 0; 25}

Warning, Error共になく、正常に動作しています。
maisumakunさんの回答を見ると、未定義とされているので怖いのですが・・・・

これぐらいしか方法がなさそうです。

間違いがあったらご指摘ください。
他にも方法があれば是非教えてください。

int main() { int (*p)() = set(0); int (*r)() = set(1); ((void (*)(void))p)(); printf("%d\n", r(1,40)); return 0; }

こちらの方が分かりやすいですかね・・・

「追記2」_________________________________
あ、この場合は未定義ではないのかな?

「型変換されたポインタを関数呼出しに用い,関数の型がポインタが指すものの型と適合しない場合,その動作は未定義とする。 」

となっているようなので、呼び出しの際に元の型へ戻しておけば動作は未定義ではない
ということだと思います。

型を別のものにキャストして呼び出した場合の話なので、

とある型 → 別の型にキャスト → 元の型にキャスト → 呼び出し なら大丈夫!!
とある型 → 別の型にキャスト → 呼び出し これはダメ!!

という事ですかな?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

C言語の世界では、「引数や返り値の違う関数」は別の型です。関数の引数や返り値は呼び出す側でのコード生成にも影響しますので、「シグネチャの違う関数ポインタにキャストして、そのまま呼び出す」ことは未定義の動作となります(≒やってはいけません)。

ある型の関数へのポインタを,別の型の関数へのポインタに型変換することができる。さらに再び型変換で元の型に戻すことができるが,その結果は元のポインタと比較して等しくなければならない。型変換されたポインタを関数呼出しに用い,関数の型がポインタが指すものの型と適合しない場合,その動作は未定義とする。

「正しい関数型に戻すのは呼んだ側の責任」でいいのであれば、まだvoid *を返したほうが、そのまま使えないだけ親切だと思います。

投稿2017/08/05 08:51

編集2017/08/05 08:57
maisumakun

総合スコア145123

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/08/05 09:50

未定義なんですか! むむ・・・・
yumetodo

2017/08/05 10:00

まあそもそもこんなハックをしたくなる時点で何かを間違えているのでプログラムの設計そのものを見直すべきでしょう。
strike1217

2017/08/05 10:15 編集

void func(){} void (*p)(void) = (void(*)(void))func; キャストしないとwarningかerrorが出ると思いますが、こんな単純な場合でも未定義なんですか? (ちなみに、こちらは正常に動作します。)
yumetodo

2017/08/05 10:24

ん?それは別にキャストいらないですし型変わっていませんよね? 関数の型が何のために存在しているか考えてください。 関数の創世から深淵まで駆け抜ける関数とはなんぞや講座 - Qiita http://qiita.com/yumetodo/items/cdfb41781d32d98be1b4
strike1217

2017/08/05 11:00

あら、warning出ませんでしたっけ?
strike1217

2017/08/05 11:01

あ、出ませんね。 勘違いでした。すいません。
guest

0

ベストアンサー

こういう要領で引数リストの中身を空にすると、引数の内容を不問にする関数ポインタというものを作ることが出来ます。

#include <stdio.h> int funcs(int a, int b) { printf("%d", a + b); return a + b; } int func(int a, int b, int c) { printf("%d", a + b + c); return a + b + c; } int (*sell(int c))() { if(c == 0) return func; else return funcs; }

C で引数が無い関数を宣言するときにわざわざ仮引数リストを (void) という風に書くのは、 () だと「引数の型情報がないものとする」という解釈になってしまうからなのです。

しかし、もちろんその関数ポインタを呼び出すときに引数の型や個数が定義と異なると未定義の挙動となります。 他の方の回答では void* を使うという提案もありますが、厳密に言えば言語仕様上は関数ポインタは void* よりも大きい幅である可能性があるのでお勧めできません。

投稿2017/08/05 09:47

SaitoAtsushi

総合スコア5437

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2017/08/05 09:55

あら、できました!! 先ほどやった時はwarningが出現していたんですが、今やったら消えました。 (謎ですが・・・) 定義とは異なる型へキャストした場合は、未定義ということですね!
strike1217

2017/08/05 10:00

返り値の方はどのようにすれば良いのでしょうか?
SaitoAtsushi

2017/08/05 11:33

返却値の方の型情報を消す方法というのはありませんが、関数ポインタ同士はキャストしても情報が失われないことは保証されています。 もちろん、呼び出すときは元の型にキャストしてからでなければなりませんが。 なので、適当な返却値の関数ポインタを使い、適宜キャストを入れるしか仕方がないと思います。 なお、 C++ では仮引数リスト `()` は `(void)` と同じ意味に解釈されることになっているという非互換があるということには留意してください。
strike1217

2017/08/05 11:49

ほぉ!! そうなんですか! わかりました。ありがとうございます。
maisumakun

2017/08/06 01:26

> 厳密に言えば言語仕様上は関数ポインタは void* よりも大きい幅である可能性 ありがとうございます。データポインタと別世界になる(こともある)ものですしね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問