プロが教える店舗&オフィスのセキュリティ対策術

C言語について質問します。

ある関数を定義するとします。
その関数は引数としてintを一つ取り、返値としてその関数と同じ型の関数へのポインタを返すようにしたいのですが、どのように書けばよいのでしょうか?
そして、その関数の型をtypedefで定義したいです。

例えば、FNをtypedefしたいその関数の型だとすると、

typedef FN (*FN)(int);

のようなFNを定義したいのですが、上のように書いても当然コンパイラ(VC9)に怒られます。
最悪、

typedef void* (*FN)(int);

とvoidポインタを返すように定義しておいて、そのポインタを返値として受け取った側でFNにキャストし直す方法で対処できなくもないですが、ちょっと強引過ぎる気がします。
何かいい方法はあるのでしょうか?

boost::functionあたりを使えばできそうな、そうでもないような気がしますが、できれば純粋なCでの解決法を望みます。
よろしくお願いします。

A 回答 (4件)

こんばんは。


More Exceptional C++の213~214ページにそっくりそのままの用途の内容がありますね。

まず
typedef FN (*FN)(int);
と言ったこと自体は確かに不可能です。

ということで、これに似た事がやりたい場合はどうするか
という方向性の話ですが


問題なのは
typedef void* (*FN)(int);
この形式では

void*は関数ポインタ以外のポインタ(オブジェクトのポインタ)はどんなものでも保持できる十分な大きさが保証されているが、関数ポインタに対してはその限りではなく

sizeof(void*) < sizeof(FN)

となるプラットフォームも存在するし、別のコンパイラにしたり、バージョンアップするだけで、動作しなくなる可能性がある
とのことです。

そう言う点だけであれば関数ポインタを使ってキャストすれば可能ではある
とは書かれています。

↓以下若干変えつつ引用、その1

/////////////////

typedef void (*VoidFP)();
typedef VoidFP (*FP)();

VoidFP f(){ return (VoidFP)f; } //VoidFPへのキャスト。

FP p = (FP)f; //VoidFPからのキャスト。
p();


このコードは技術的に正しい。

しかし、型システムを故意に破っている点で危険であり、f()の全ユーザにキャストの使用を強制する点で望ましくない。

/////////////////

とのことです。

そしてC++のコードになりますが
「正しく、かつ移植性のある方法」と銘打たれている方法は

若干変えつつ引用、その2
/////////////////

class FP_;
typedef FP_ (*FP)();

class FP_ {
FP p_;
public:
FP_( FP p ) : p_(p){}
operator FP(){ return p_; }
};

これで、FPオブジェクトの宣言、定義、および自然な呼び出しが可能になる。

FP_ f(){ return f; }

int main(){
FP p = f(); //自然な呼び出しのシンタックス
p();
}

/////////////////

とのことですね。
    • good
    • 0
この回答へのお礼

回答が遅くなりまして申し訳ありません。
(More) Essential C++が一通り目を通しましたけど、More Exceptional C++は見たことがないですね。有益な情報をありがとうございます。

>void*は関数ポインタ以外のポインタ(オブジェクトのポインタ)はどんなものでも保持できる十分な大きさが保証されているが、関数ポインタに対してはその限りではなく

あ、そうなんですか。奥が深いですね、C/C++は。
確かに純粋なハーバードアーキテクチャで、プログラム空間は広い(32bitアドレス)けど、データ空間は狭い(16bitアドレス)なんてCPUがあるかもしれませんね。すごくニッチな世界だと思いますが・・・。

その2の方法は、なるほどと思いました。関数ポインタを返そうとするのではなく、関数オブジェクトでラッピングして返すということですね。
# これがNo.1さんのいう「関数オブジェクトを使えば余裕」ということなんだろうか・・・

非常に参考になりました。ありがとうございました。

お礼日時:2012/04/17 02:42

すでに指摘されているように、void*と関数へのポインタではサイズが異なる可能性があります。


そこで、こんな感じでどうでしょうか?

#include <stdio.h>

typedef void (* (*FN)(int) )(struct { int fake; });

#define call_FN(fn, arg)  (FN)(((FN)(fn))(arg))

void (* fn1(int arg) )(struct { int fake; })
{
  printf("%d\n", arg);
}

void (* fn2(int arg) )(struct { int fake; })
{
  printf("%d\n", arg);
  return fn1;
}

int main(void)
{
  FN fn = fn2;
  fn = call_FN(fn, 123);
  fn = call_FN(fn, 456);
  return 0;
}

警告は出るかもしれませんが、ほぼ期待したものに近いことができると思います。
仮引数として無名構造体を受け取るようにすることで、call_FNマクロを介さずに呼び出そうとすると、確実にエラーを発生させられるように工夫しています。
    • good
    • 0
この回答へのお礼

返事が遅くなりまして申し訳ありません。

マクロを使ってキャストを隠蔽するというアイデアはあったのですが、マクロを介さなければエラーを出す、というアイデアとその手法は斬新ですね。
ただ、通常の関数呼び出しとはちょっと見え方が異なる、という点は残念ですね。まあ、呼び出し方が意味は分かるし、このくらいは非常に些細な点ですから、十分受け入れられる範囲だと思います。

おもしろいアイデアをありがとうございます。

お礼日時:2012/04/17 02:50

不可能です。


(1)typedefの前にFNが未定義
 FNが不明だからエラー
(2)typedefの前にFNが定義済み
 二重定義なのでエラー
    • good
    • 0
この回答へのお礼

返事が遅くなって申し訳ありません。
ご丁寧にエラーの説明までしていただいてありがとうございます。

エラーになることは理解していますが、やりたいことを説明する方法として
> typedef FN (*FN)(int);
という書き方を敢えてした、とご理解いただければと思います。

お礼日時:2012/04/17 02:31

「純粋な C」ではキャストが必須. C++ なら関数オブジェクトを返せばいいので余裕.

    • good
    • 0
この回答へのお礼

回答が遅くなって申し訳ありません。

「純粋なC」と書きましたが、書き間違いでC++はOKです。boostのような複雑なライブラリは無しでやりたかった、というのが本当の意図でした。

で、「関数オブジェクト」を返すという方法は考えつかなかったです。頭が固いのか、「余裕」でできるそうなんですが具体的な方法がまだ思いついていない状況です。

有益なヒントを与えていただいたことに感謝します。ありがとうございました。

お礼日時:2012/04/17 02:29

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!