web-dev-qa-db-ja.com

関数を返す関数を作成するにはどうすればよいですか?

全体像:関数を備えたモジュールと、それらの関数に対するプロシージャと関数を備えたモジュールがあります。

(関数のモジュールインターフェイスから)2つの関数を組み合わせると:

double f1(double alpha, double x);
double f2(double beta, double x);

いくつかの方法で(そのうちの1つは追加しています):

double OP_Addition(double (*f)(double,double) , double (*g)(double,double), double param1, double param2, double x);

次の(一部の)実装では問題ありません。

z1 = (*f)(param1, x);
z2 = (*g)(param2, x);
y = z1 + z2;
return y;

しかし、「新しい」関数へのポインタを返したい場合は、次のようになります。

void *OP_PAdd( double (*f)(double,double), double param3 );

私はそれを正しく動作させることができず、正しい「電話」をかけることもできません。出力「関数」を他の関数の入力として使用したい。

39
MCL

他の答えは正しくて興味深いですが、ポータブルC99では、C関数として本物の クロージャ を使用する方法がないことに注意してください(これはCの基本的な制限です)。クロージャが何であるかわからない場合は、 the wikiページを注意深く読んでください(また、 [〜#〜] sicp [〜#〜] 、特にその-を読んでください。 §1. )。ただし、 C++ 11 では、 std :: function および lambda-expressions を使用してクロージャがあることに注意してください。そして、他のほとんどのプログラミング言語(Ocaml、Haskell、Javascript、LISP、Clojure、Pythonなど)にはクロージャがあります。

Cには本物のクロージャがないため(「数学的に」C関数の閉じた値はグローバル変数または静的変数またはリテラルのみです)、C関数ポインタを受け入れるほとんどのライブラリはAPI処理を提供します コールバック 一部のクライアントデータ(簡単な例は qsort_r ですが、もっと真剣に内部を見てください [〜#〜] gtk [〜#〜] )。そのクライアントデータ(通常は不透明なポインター)を使用して、閉じた値を保持できます。おそらく同様の規則に従う必要があるため(関数ポインターをいくつかの追加のクライアントデータを含むコールバックとして体系的に渡す)、C関数の署名を変更する必要があります(生の関数ポインターだけを渡すのではなく、渡すクロージャを「エミュレート」するための、コールバックとしての関数ポインタと一部のクライアントデータの両方)。

実行時にC関数を生成できる場合があります(おそらくオペレーティングシステムまたは外部ライブラリの助けを借りて、非標準機能を使用します)。 JITコンパイル ライブラリ GNU lightninglibjit (どちらも実行速度の遅いコードをすばやく生成します)、 asmjit (各マシン命令を明示的に生成し、高速のx86-64コードを出力するのはユーザーの責任です)、 [〜#〜] gccjit [〜#〜] または [〜#〜] llvm [〜#〜] (どちらも既存のコンパイラより上にあるため、最適化されたコードを少しゆっくりと出力するために使用できます)。 POSIXおよびLinuxシステムでは、一時ファイルにCコードを出力することもできます/tmp/tempcode.c、コンパイルをフォークします(例:gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so)そのコードをプラグインにロードし、生成されたプラグインを dlopen(3)dlsym(3) ..を使用して動的にロードします。

ところで、コーディングしている実際のアプリケーションが何であるかはわかりませんが、その中にインタプリタを埋め込むことを検討してください。 Lua または Guile 。次に、組み込みのエバリュエーター/インタープリターを使用してコールバックを提供します。

24

別の関数から関数を返す場合、これを行う最もクリーンな方法はtypedefを使用することです。

typedef double (*ftype)(double, double);

次に、次のように関数を宣言できます。

ftype OP_PAdd( ftype f, double param3 )
{
    ....
    return f1;
}

typedefなしでこれを行うことができますが、面倒です。

double (*OP_PAdd( double (*f)(double,double), double param3 ))(double,double)
{
    return f1;
}

したがって、パラメータまたは他の関数の戻り値として関数ポインタがある場合は、typedefを使用します。

編集:

次のように型を宣言できますが、

typedef double ftype(double, double);

実際には、このようなタイプを直接使用することはできません。関数は関数を返すことができず(関数へのポインターのみ)、このタイプの変数を割り当てることはできません。

また、関数を呼び出すために関数ポインターを明示的に逆参照する必要がないため、ポインター自体が非表示になっていることは大きな問題ではありません。関数ポインタをtypedefとして定義することも慣例です。 signalのマニュアルページ から:

   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);
32
dbush

このような意味ですか? decider()関数は、別の関数へのポインターを返し、それが呼び出されます。

_#include <stdio.h>
#include <stdlib.h>

typedef double(*fun)(double, double);

double add(double a, double b) {
    return a + b;
}

double sub(double a, double b) {
    return a - b;
}

double mul(double a, double b) {
    return a * b;
}

fun decider(char op) {
    switch(op) {
        case '+': return add;
        case '-': return sub;
        case '*': return mul;
    }
    exit(1);
}

int main(void)
{
    fun foo;

    foo = decider('+');
    printf("%f\n", foo(42.0, 24.0));

    foo = decider('-');
    printf("%f\n", foo(42.0, 24.0));

    foo = decider('*');
    printf("%f\n", foo(42.0, 24.0));

    return 0;
}
_

プログラム出力:

_66.000000
18.000000
1008.000000
_

EDIT:@ dbush answerの下のコメントに続いて、このバージョンはポインタとしてのtypedefから関数だけに戻ります。同じ出力が得られますが、decider()では、_return add;_または_return &add;_のどちらを記述しても、正しくコンパイルされ、正しい出力が得られます。

_#include <stdio.h>
#include <stdlib.h>

typedef double(fun)(double, double);

double add(double a, double b) {
    return a + b;
}

double sub(double a, double b) {
    return a - b;
}

double mul(double a, double b) {
    return a * b;
}

fun *decider(char op) {
    switch(op) {
        case '+': return add;     // return &add;
        case '-': return sub;
        case '*': return mul;
    }
    exit(1);
}

int main(void)
{
    fun *foo;

    foo = decider('+');
    printf("%f\n", foo(42.0, 24.0));

    foo = decider('-');
    printf("%f\n", foo(42.0, 24.0));

    foo = decider('*');
    printf("%f\n", foo(42.0, 24.0));

    return 0;
}
_
13
Weather Vane

cでは、関数へのポインタを返すことができますが、そのためには、関数が最初に存在する必要があり、関数を動的に作成することはCが可能だと言っていることではありません。その方法を気にしないでください。

コードが1つのOSと1つのプロセッサ(およびおそらく他のいくつかの制限)でのみ機能する場合は、次のことができる可能性があります。

  1. メモリのページを割り当てる
  2. ポインタで渡された関数の呼び出しなど、必要な処理を実行するデータとマシンコードを記述します。
  3. メモリ保護を読み取り/書き込みから読み取り/実行に変更します
  4. 作成した関数へのポインタを返す
  5. 機能ごとに4kBが必要であることを心配しないでください

そのためのライブラリはおそらくどこかにありますが、必ずしも移植可能ではありません

3
Jordan Szubert

したがって、関数が関数へのポインタを返すようにします。

double retfunc()
{
   return 0.5;
}

double (*fucnt)()
{
  return retfunc;
}

main()
{
   printf("%f\n", (*funct())());
}
0
Bing Bang

一部の人々はこの問題を解決するためにハックを書くことについて明らかに妄想的であるため、これを行うためのハックの少ない方法があります:setjmpとlongjmpで静的構造体を使用します。

jmp_buf jb;

void *myfunc(void) {
    static struct {
        // put all of your local variables here.
        void *new_data, *data;
        int i;
    } *_;
    _ = malloc(sizeof(*_));
    _.data = _;
    if (!(_.new_data = (void *)(intptr_t)setjmp(jb)))
        return _.data;
    _.data = _.new_data;
    /* put your code here */
    free(_);
    return NULL;
}

ここで何が起こっているかを説明するために、setjmpはジャンプバッファの作成時に値0を返します。それ以外の場合は、longjmpによって渡された値を返します(たとえば、longjmp(jb、5)はsetjmpに5を返します)。

つまり、私たちが行っているのは、割り当てられたデータ構造へのポインタを関数に返すことです。そして、次のようにクロージャを呼び出します。

void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);

Intは、すべてのプラットフォームでポインタを格納するのに十分な大きさであるとは限らないことに注意してください。そのため、データプールを作成し、ハンドル(プール内のインデックス)でデータを返す/渡す必要がある場合があります。

前に述べたように、クロージャは、すべてのデータがヒープに割り当てられた関数にすぎません。

私は何年もの間、N64およびPSPゲームのハックを書いてきました。これは不可能だと主張する人々は、おそらくそのようなものをいじったことがないでしょう。ほとんどの場合、経験不足に要約されます。

0
DeftlyHacked