web-dev-qa-db-ja.com

Cでクロージャーを達成する方法はありますか

これを機能させたいのですが、機能しません。

#include <stdio.h>

typedef struct closure_s {
  void (*incrementer) ();
  void (*emitter) ();
} closure;

closure emit(int in) {

  void incrementer() {
    in++;
  }

  void emitter() {
    printf("%d\n", in);
  }

  return (closure) {
    incrementer,
    emitter
  };
}

main() {
  closure test[] = {
    emit(10),
    emit(20)
  };

  test[0] . incrementer();
  test[1] . incrementer();

  test[0] . emitter();
  test[1] . emitter();
}

実際にはdoesコンパイルされ、1つのインスタンスで機能します... 2番目のインスタンスは失敗します。 Cでクロージャーを取得する方法はありますか?

それは本当に素晴らしいでしょう!

33
kristopolous

[〜#〜] ffcall [〜#〜] を使用して、

#include <callback.h>
#include <stdio.h>
static void incrementer_(int *in) {
    ++*in;
}
static void emitter_(int *in) {
    printf("%d\n", *in);
}
int main() {
    int in1 = 10, in2 = 20;
    int (*incrementer1)() = alloc_callback(&incrememnter_, &in1);
    int (*emitter1)() = alloc_callback(&emitter_, &in1);
    int (*incrementer2)() = alloc_callback(&incrememnter_, &in2);
    int (*emitter2)() = alloc_callback(&emitter_, &in2);
    incrementer1();
    incrementer2();
    emitter1();
    emitter2();
    free_callback(incrementer1);
    free_callback(incrementer2);
    free_callback(emitter1);
    free_callback(emitter2);
}

しかし、通常Cでは、偽のクロージャに余分な引数を渡すことになります。


Appleには、Cに対する非標準の拡張機能 blocks があります。これは、クロージャーのように機能します。

19
ephemient

GCCとclangにはブロック拡張があります。これは基本的にCのクロージャーです。

7
arsenm

ANSI Cは、入れ子関数と同様に、クロージャをサポートしていません。その回避策は、使用法の単純な「構造体」です。

2つの数値を合計する簡単な例のクロージャ。

// Structure for keep pointer for function and first parameter
typedef struct _closure{
    int x;
    char* (*call)(struct _closure *str, int y);
} closure;


// An function return a result call a closure as string
char *
sumY(closure *_closure, int y) {
    char *msg = calloc(20, sizeof(char));
    int sum = _closure->x + y;
    sprintf(msg, "%d + %d = %d", _closure->x, y, sum);
    return msg;
}


// An function return a closure for sum two numbers
closure *
sumX(int x) {
    closure *func = (closure*)malloc(sizeof(closure));
    func->x = x;
    func->call = sumY;
    return func;
}

使用法:

int main (int argv, char **argc)
{

    closure *sumBy10 = sumX(10);
    puts(sumBy10->call(sumBy10, 1));
    puts(sumBy10->call(sumBy10, 3));
    puts(sumBy10->call(sumBy10, 2));
    puts(sumBy10->call(sumBy10, 4));
    puts(sumBy10->call(sumBy10, 5));
}

結果:

10 + 1 = 11
10 + 3 = 13
10 + 2 = 12
10 + 4 = 14
10 + 5 = 15

C++ 11では、ラムダ式を使用して実現します。

#include <iostream>
int main (int argv, char **argc)
{
    int x = 10;
    auto sumBy10 = [x] (int y) {
        std::cout << x << " + " << y << " = " << x + y << std::endl;
    };
    sumBy10(1);
    sumBy10(2);
    sumBy10(3);
    sumBy10(4);
    sumBy10(5);
}

フラグ-std = c ++ 11を指定してコンパイルした後の結果。

10 + 1 = 11
10 + 2 = 12
10 + 3 = 13
10 + 4 = 14
10 + 5 = 15
4
Seti Volkylany

JavaScriptの例を使用したクロージャの実際の定義の例

クロージャは、関数が必要とするデータのインスタンスとともに実行される関数へのポインタまたはある種の参照を含む一種のオブジェクトです。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures のJavaScriptの例は次のとおりです。

_function makeAdder(x) {
  return function(y) { // create the adder function and return it along with
    return x + y;      // the captured data needed to generate its return value
  };
}
_

その後、次のように使用できます。

_var add5 = makeAdder(5);  // create an adder function which adds 5 to its argument

console.log(add5(2));  // displays a value of 2 + 5 or 7
_

Cで克服する障害の一部

JavaScriptとは異なり、Cプログラミング言語は静的に型付けされた言語であり、ガベージコレクションや、JavaScriptや、クロージャーを本質的にサポートする他の言語でクロージャーを簡単に実行できるようにする他のいくつかの機能も備えていません。

標準Cでのクロージャーの大きな障害の1つは、JavaScriptの例のような構成の言語サポートがないことです。この例では、クロージャーには関数だけでなく、クロージャーの作成時にキャプチャされるデータのコピーも含まれています。保存状態。これは、クロージャーが実行されたときに、クロージャー関数が呼び出されたときに提供された追加の引数とともに使用できます。

ただし、Cには、一種のクロージャーを作成するためのツールを提供できるいくつかの基本的なビルディングブロックがあります。いくつかの困難は、(1)メモリ管理はプログラマの義務であり、ガベージコレクションはありません、(2)関数とデータは分離されており、クラスまたはクラスタイプのメカニズムはありません。またはデータサイズ、および(4)クロージャの作成時に状態データをキャプチャするための貧弱な言語機能。

Cでクロージャ機能を可能にする1つのことは、_void *_ポインタと、_unsigned char_を一種の汎用メモリタイプとして使用し、キャストによって他のタイプに変換されることです。

標準Cを使用した実装と、あちこちに伸びるビット

注:次の例は、ほとんどのx86 32ビットコンパイラで使用されているスタックベースの引数渡し規則に依存しています。ほとんどのコンパイラでは、Visual Studioの___fastcall_修飾子などのスタックベースの引数渡し以外の呼び出し規約を指定することもできます。 x64および64ビットVisual Studioのデフォルトでは、デフォルトで___fastcall_規則を使用して、関数の引数がスタックではなくレジスターで渡されるようにします。 Microsoft MSDNのx64呼び出し規約の概要Windowsの64ビットアプリケーションでランタイム中にアセンブリの関数引数を設定する方法 を参照してください。 可変引数はどのようにgccに実装されていますか?のコメント

私たちにできることの1つは、Cに何らかの閉鎖機能を提供するというこの問題を解決することです。これは問題を単純化することです。ほとんどのアプリケーションで有用な80%ソリューションを提供する方が、ソリューションをまったく提供しないよりも優れています。

そのような単純化の1つは、値を返さない関数、つまりvoid func_name()として宣言された関数のみをサポートすることです。このアプローチは実行時に関数の引数リストを作成するため、関数の引数リストのコンパイル時の型チェックも中止します。私たちが断念しているこれらのことのいずれも些細なことではないので、問題は、Cでのクロージャーに対するこのアプローチの価値が、断念しているものを上回るかどうかです。

まず最初に、閉鎖データ領域を定義しましょう。クロージャデータ領域は、クロージャに必要な情報を格納するために使用するメモリ領域を表します。私が考えることができるデータの最小量は、実行する関数へのポインタと、引数として関数に提供されるデータのコピーです。

この場合、関数に必要なキャプチャされた状態データを関数の引数として提供します。

また、合理的に安全に失敗するように、いくつかの基本的な安全対策を講じたいと考えています。残念ながら、安全性Railsは、クロージャの形式を実装するために使用しているいくつかの回避策があるため、少し弱いです。

ソースコード

次のソースコードは、Visual Studio 2017 Community Editionを使用して.c Cソースファイルで開発されました。

データ領域は、いくつかの管理データ、関数へのポインタ、およびオープンエンドのデータ領域を含む構造体です。

_typedef struct {
    size_t  nBytes;    // current number of bytes of data
    size_t  nSize;     // maximum size of the data area
    void(*pf)();       // pointer to the function to invoke
    unsigned char args[1];   // beginning of the data area for function arguments
} ClosureStruct;
_

次に、クロージャデータ領域を初期化する関数を作成します。

_ClosureStruct * beginClosure(void(*pf)(), int nSize, void *pArea)
{
    ClosureStruct *p = pArea;

    if (p) {
        p->nBytes = 0;      // number of bytes of the data area in use
        p->nSize = nSize - sizeof(ClosureStruct);   // max size of the data area
        p->pf = pf;         // pointer to the function to invoke
    }

    return p;
}
_

この関数は、関数のユーザーがメモリをどのように管理したいかについて柔軟性を与えるデータ領域へのポインタを受け入れるように設計されています。スタック上のメモリまたはスタティックメモリを使用するか、malloc()関数を介してヒープメモリを使用できます。

_unsigned char closure_area[512];
ClosureStruct *p = beginClosure (xFunc, 512, closure_area);
_

または

_ClosureStruct *p = beginClosure (xFunc, 512, malloc(512));
//  do things with the closure
free (p);  // free the malloced memory.
_

次に、クロージャーにデータと引数を追加できるようにする関数を提供します。この関数の目的は、クロージャー関数が呼び出されたときに、クロージャー関数がその仕事を行うために必要なデータを提供できるように、クロージャーデータを構築することです。

_ClosureStruct * pushDataClosure(ClosureStruct *p, size_t size, ...)
{
    if (p && p->nBytes + size < p->nSize) {
        va_list jj;

        va_start(jj, size);    // get the address of the first argument

        memcpy(p->args + p->nBytes, jj, size);  // copy the specified size to the closure memory area.
        p->nBytes += size;     // keep up with how many total bytes we have copied
        va_end(jj);
    }

    return p;
}
_

そして、これを少し簡単に使用できるようにするために、一般的に便利なラッピングマクロを提供しますが、Cプロセッサのテキスト操作であるため制限があります。

_#define PUSHDATA(cs,d) pushDataClosure((cs),sizeof(d),(d))
_

そのため、次のソースコードのようなものを使用できます。

_unsigned char closurearea[256];
int  iValue = 34;

ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, closurearea), iValue);
dd = PUSHDATA(dd, 68);
execClosure(dd);
_

クロージャの呼び出し:execClosure()関数

これの最後の部分は、データを使用してクロージャ関数を実行するexecClosure()関数です。この関数で行っているのは、関数を呼び出すときに、クロージャーデータ構造で提供される引数リストをスタックにコピーすることです。

クロージャーデータのargs領域を_unsigned char_配列を含む構造体へのポインターにキャストし、ポインターを逆参照して、Cコンパイラが引数のコピーをスタックに呼び出してから、クロージャで機能します。

execClosure()関数を簡単に作成できるように、必要なさまざまなサイズの構造体を簡単に作成できるマクロを作成します。

_// helper macro to reduce type and reduce chance of typing errors.

#define CLOSEURESIZE(p,n)  if ((p)->nBytes < (n)) { \
struct {\
unsigned char x[n];\
} *px = (void *)p->args;\
p->pf(*px);\
}
_

次に、このマクロを使用して一連のテストを作成し、クロージャー関数を呼び出す方法を決定します。ここで選択したサイズは、特定のアプリケーションでは微調整が必​​要になる場合があります。これらのサイズは任意であり、クロージャデータが同じサイズになることはめったにないため、スタックスペースを効率的に使用していません。また、許可された数より多くの閉鎖データが存在する可能性があります。

_// execute a closure by calling the function through the function pointer
// provided along with the created list of arguments.
ClosureStruct * execClosure(ClosureStruct *p)
{
    if (p) {
        // the following structs are used to allocate a specified size of
        // memory on the stack which is then filled with a copy of the
        // function argument list provided in the closure data.
        CLOSEURESIZE(p,64)
        else CLOSEURESIZE(p, 128)
        else CLOSEURESIZE(p, 256)
        else CLOSEURESIZE(p, 512)
        else CLOSEURESIZE(p, 1024)
        else CLOSEURESIZE(p, 1536)
        else CLOSEURESIZE(p, 2048)
    }

    return p;
}
_

簡単に利用できるように、クロージャーへのポインターを返します。

開発されたライブラリを使用した例

上記は次のように使用できます。最初に、実際にはあまり機能しないいくつかの関数の例を示します。

_int zFunc(int i, int j, int k)
{
    printf("zFunc i = %d, j = %d, k = %d\n", i, j, k);
    return i + j + k;
}

typedef struct { char xx[24]; } thing1;

int z2func(thing1 a, int i)
{
    printf("i = %d, %s\n", i, a.xx);
    return 0;
}
_

次に、クロージャーを作成して実行します。

_{
    unsigned char closurearea[256];
    thing1 xpxp = { "1234567890123" };
    thing1 *ypyp = &xpxp;
    int  iValue = 45;

    ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
    free(execClosure(PUSHDATA(dd, iValue)));

    dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
    dd = PUSHDATA(dd, 68);
    execClosure(dd);

    dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
    dd = PUSHDATA(dd, 145);
    dd = PUSHDATA(dd, 185);
    execClosure(dd);
}
_

これはの出力を与えます

_i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
_

カレーはどうですか?

次に、クロージャー構造に変更を加えて、関数のカリー化を行うことができます。

_typedef struct {
    size_t  nBytes;    // current number of bytes of data
    size_t  nSize;     // maximum size of the data area
    size_t  nCurry;    // last saved nBytes for curry and additional arguments
    void(*pf)();       // pointer to the function to invoke
    unsigned char args[1];   // beginning of the data area for function arguments
} ClosureStruct;
_

カレーポイントのカレーとリセットのためのサポート機能付き

_ClosureStruct *curryClosure(ClosureStruct *p)
{
    p->nCurry = p->nBytes;
    return p;
}
ClosureStruct *resetCurryClosure(ClosureStruct *p)
{
    p->nBytes = p->nCurry;
    return p;
}
_

これをテストするためのソースコードは次のようになります。

_{
    unsigned char closurearea[256];
    thing1 xpxp = { "1234567890123" };
    thing1 *ypyp = &xpxp;
    int  iValue = 45;

    ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
    free(execClosure(PUSHDATA(dd, iValue)));
    dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
    dd = PUSHDATA(dd, 68);
    execClosure(dd);
    dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
    dd = PUSHDATA(dd, 145);
    dd = curryClosure(dd);
    dd = resetCurryClosure(execClosure(PUSHDATA(dd, 185)));
    dd = resetCurryClosure(execClosure(PUSHDATA(dd, 295)));
}
_

の出力で

_i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
zFunc i = 45, j = 145, k = 295
_
2

GCCは内部関数をサポートしていますが、クロージャーはサポートしていません。 C++ 0xにはクロージャーがあります。私が認識しているCのバージョンはなく、確かに標準バージョンはありません。

Phoenix はBoostの一部であり、C++でクロージャーを提供します。

2
outis

このページでは、Cでクロージャーを行う方法についての説明を見つけることができます。

http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/

考え方としては、構造体が必要であり、その構造体には関数ポインターが含まれていますが、最初の引数として関数に提供されます。多くのボイラープレートコードが必要であり、メモリ管理が当然問題であるという事実は別として、これは機能し、他の言語のクロージャーの能力と可能性を提供します。

1
user3761804