5

関数をCの関数に一般的に渡すことができるようにしたいと思います。私はCを数年間使用しており、適切なクロージャと高階関数を実装する際の障壁を認識しています。それはほとんど乗り越えられないです。

StackOverflowを調べて、他の情報源がこの問題について何と言っているかを確認しました。

...そして、varargsまたはアセンブリを使用する以外に、銀の弾丸の一般的な答えはありませんでした。アセンブリの骨はありませんが、ホスト言語で機能を効率的に実装できるのであれば、通常はそうしようとしています。

なかなかHOFが持てないので…

高階関数が欲しいのですが、ピンチでデリゲートに落ち着きます。以下のコードのようなもので、Cで実行可能なデリゲート実装を取得できると思います。

このような実装が思い浮かびます。

enum FUN_TYPES {
    GENERIC,
    VOID_FUN,
    INT_FUN,
    UINT32_FUN,
    FLOAT_FUN,
};

typedef struct delegate {
    uint32 fun_type;
    union function {
        int (*int_fun)(int);
        uint32 (*uint_fun)(uint);
        float (*float_fun)(float);
        /* ... etc. until all basic types/structs in the 
           program are accounted for. */
    } function;
} delegate;

使用例:

void mapint(struct fun f, int arr[20]) {
    int i = 0;
    if(f.fun_type == INT_FUN) {
        for(; i < 20; i++) {
            arr[i] = f.function.int_fun(arr[i]);
        }
    }
}


残念ながら、デリゲートに対するこのアプローチには明らかな欠点がいくつかあります。

  • タイプチェックはありません。「fun_type」フィールドをチェックして自分で行うタイプを保存してください。
  • タイプチェックにより、コードに追加の条件が導入され、以前よりも乱雑で分岐が多くなります。
  • 関数の(安全な)可能な順列の数は、「fun_type」変数のサイズによって制限されます。
  • 関数ポインタ定義の列挙型とリストは、マシンで生成する必要があります。些細な場合を除いて、他のものは狂気に接するでしょう。
  • 悲しいことに、通常のCを通過することは、たとえばmov-> callシーケンスほど効率的ではありません。これは、おそらくアセンブリで実行できます(多少の困難が伴います)。


Cのデリゲートのようなことをするためのより良い方法を知っている人はいますか?

注:移植性と効率性が高いほど、優れています

また、注: C++用のDonClugstonの非常に高速なデリゲートについて聞いたことがあります。ただし、C++ソリューションには興味がありません。Cだけです。

4

2 に答える 2

2

すべての関数に引数を追加してvoid*、バインドされた引数や委任などを許可できます。残念ながら、外部関数と関数ポインターを処理するものにはラッパーを作成する必要があります。

于 2013-01-15T05:58:11.430 に答える
1

基本的なテクニックのわずかに異なるバージョンを提供する類似したもののテクニックを調査した2つの質問があります。これの欠点は、引数リストが実行時に作成されるため、コンパイル時のチェックが失われることです。

1つ目は、Cでカリー化する方法はありますかという質問に対する私の答えです。このアプローチでは、プロキシ関数を使用して、関数ポインターと関数の引数を呼び出します。

2つ目は、質問Cに対する私の答えです。LoadLibrary()からインポートされた関数にvoid-pointer-listとして引数を渡します

基本的な考え方は、引数リストを作成するために使用されるメモリ領域を用意し、関数の呼び出しの一部としてそのメモリ領域をスタックにプッシュすることです。その結果、呼び出された関数はメモリ領域をパラメータのリストとして認識します。

Cで重要なのはstruct、メモリ領域として使用される配列を含むを定義することです。呼び出された関数が呼び出されると、全体structが値によって渡されます。つまり、配列に設定された引数がスタックにプッシュされ、呼び出された関数がstruct値ではなく引数のリストを認識できるようになります。

カレーの質問への回答では、メモリ領域に関数ポインタと1つ以上の引数、一種のクロージャが含まれています。次に、メモリ領域はプロキシ関数に渡されます。プロキシ関数は、クロージャ内の引数を使用して関数を実際に呼び出します。

これが機能するのは、標準のC関数呼び出しが引数をスタックにプッシュし、関数を呼び出し、関数が戻ると、呼び出し元が実際にスタックにプッシュされたものを認識しているため、スタックをクリーンアップするためです。

于 2017-07-25T21:22:15.173 に答える