13

染められた関数型プログラマーとして、私は自分の好きなパラダイムを、私が使用しているどの言語にも当てはめようとしないのは難しいと感じています。いくつかのCIを書いているときに、関数の1つをカレーしてから、部分的に適用された関数を渡したいことがわかりました。読んだ後Cでカリー化する方法はありますか?そして、私が思いついたhttp://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functionsの警告に注意してください。

#include <stdio.h>

typedef int (*function) (int);

function g (int a) {
    int f (int b) {
        return a+b;
    }
    return f;
}

int f1(function f){
    return f(1);}

int main () {
    printf ("(g(2))(1)=%d\n",f1(g(2)));
}

これは期待どおりに実行されます。ただし、元のプログラムはdoublesで動作するため、適切なタイプを変更するだけで問題ないと思いました。

#include <stdio.h>

typedef double (*function) (double);

function g (double a) {
    double f (double b) {
        return a+b;
    }
    return f;
}

double f1(function f){
    return f(1);}

int main () {
    printf ("(g(2))(1)=%e\n",f1(g(2)));
}

これにより、次のようなものが生成されます。

bash-3.2$ ./a.out 
Segmentation fault: 11
bash-3.2$ ./a.out 
Illegal instruction: 4

エラーの選択は一見ランダムに見えます。さらに、いずれかの例が-O3コンパイラー自体でコンパイルされた場合、それ自体がスローされSegmentation fault: 11ます。どの時点でもgccから警告が表示されず、何が起こっているのか理解できません。最初のプログラムが失敗するのに、なぜ2番目のプログラムが失敗するのか誰かが知っていますか?それとも、2番目のものを修正する方法はもっと良いですか?

私のgccはでi686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)、私のカーネルはDarwin Kernel Version 12.1.0: Tue Aug 14 13:29:55 PDT 2012; root:xnu-2050.9.2~1/RELEASE_X86_64です。

編集:明確にするために、私がやろうとしていることは愚かであることを理解しています。このコードは、CuriosityローバーまたはNYSEでは実行されません。(GNU)Cの関数ポインターがどのように機能するかをもっと理解し、見つけた興味深いことを説明しようとしています。現実の世界では、このようなことは絶対にしないと約束します。

4

4 に答える 4

6

興味深い質問で、引用された回答の論文を調べました(C / C ++ / Objective-CでのCurried関数を使用したより機能的な再利用性)。

したがって、以下はあなたが行きたいと思うかもしれない場所への提案された道です。関数型プログラマーではないので、この論文が何について話しているのか完全には理解していないので、これは本当にカリー化された関数ではないと思います。ただし、少し作業を行うと、この概念のいくつかの興味深いアプリケーションが見つかります。一方、これがあなたが望むものであるかどうかはわかりませんが、Cでこれを実行できることは私の心を打つものです。

2つの問題があるようでした。

まず第一に、任意の引数リストを使用して任意の関数呼び出しを処理できるようにすることでした。私が採用したアプローチは、標準Cライブラリの変数引数機能(va_listとva_start()、va_arg()、およびva_end()関数)を使用し、関数ポインターと提供された引数をデータ領域に格納して、それらがデータ領域になるようにすることでした。その後、後で実行できます。printf()関数がフォーマット行を使用して、提供されている引数とその型の数を知る方法を借用して変更しました。

次は、関数とその引数リストの格納でした。コンセプトを試すために、任意のサイズの構造体を使用しました。これにはもう少し考える必要があります。

この特定のバージョンは、スタックのように扱われる配列を使用します。任意の関数とその引数をスタック配列にプッシュするために使用する関数と、最上位の関数とその引数をスタック配列からポップして実行する関数があります。

ただし、実際には、ハッシュマップなど、ある種のコレクションに任意の構造体オブジェクトを含めることができます。これは非常に便利な場合があります。

この論文からシグナルハンドラーの例を借りて、この概念がその種のアプリケーションで機能することを示しました。

これがソースコードです。解決策を考え出すのに役立つことを願っています。

他の引数タイプを処理できるようにするには、スイッチに他のケースを追加する必要があります。概念実証のためにいくつか実行しました。

また、これは関数を呼び出す関数を実行しませんが、表面上はかなり単純な拡張であるように見えます。私が言ったように、私はこのカレーのものを完全に手に入れるわけではありません。

#include <stdarg.h>
#include <string.h>

// a struct which describes the function and its argument list.
typedef struct {
    void (*f1)(...);
    // we have to have a struct here because when we call the function,
    // we will just pass the struct so that the argument list gets pushed
    // on the stack.
    struct {
        unsigned char myArgListArray[48];   // area for the argument list.  this is just an arbitray size.
    } myArgList;
} AnArgListEntry;

// these are used for simulating a stack.  when functions are processed
// we will just push them onto the stack and later on we will pop them
// off so as to run them.
static unsigned int  myFunctionStackIndex = 0;
static AnArgListEntry myFunctionStack[1000];

// this function pushes a function and its arguments onto the stack.
void pushFunction (void (*f1)(...), char *pcDescrip, ...)
{
    char *pStart = pcDescrip;
    AnArgListEntry MyArgList;
    unsigned char *pmyArgList;
    va_list argp;
    int     i;
    char    c;
    char   *s;
    void   *p;

    va_start(argp, pcDescrip);

    pmyArgList = (unsigned char *)&MyArgList.myArgList;
    MyArgList.f1 = f1;
    for ( ; *pStart; pStart++) {
        switch (*pStart) {
            case 'i':
                // integer argument
                i = va_arg(argp, int);
                memcpy (pmyArgList, &i, sizeof(int));
                pmyArgList += sizeof(int);
                break;
            case 'c':
                // character argument
                c = va_arg(argp, char);
                memcpy (pmyArgList, &c, sizeof(char));
                pmyArgList += sizeof(char);
                break;
            case 's':
                // string argument
                s = va_arg(argp, char *);
                memcpy (pmyArgList, &s, sizeof(char *));
                pmyArgList += sizeof(char *);
                break;
            case 'p':
                // void pointer (any arbitray pointer) argument
                p = va_arg(argp, void *);
                memcpy (pmyArgList, &p, sizeof(void *));
                pmyArgList += sizeof(void *);
                break;
            default:
                break;
        }
    }
    va_end(argp);
    myFunctionStack[myFunctionStackIndex] = MyArgList;
    myFunctionStackIndex++;
}

// this function will pop the function and its argument list off the top
// of the stack and execute it.
void doFuncAndPop () {
    if (myFunctionStackIndex > 0) {
        myFunctionStackIndex--;
        myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
    }
}

// the following are just a couple of arbitray test functions.
// these can be used to test that the functionality works.
void myFunc (int i, char * p)
{
    printf (" i = %d, char = %s\n", i, p);
}

void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s\n", i, p, p2);
}

void mySignal (int sig, void (*f)(void))
{
    f();
}

int main(int argc, char * argv[])
{
    int i = 3;
    char *p = "string";
    char *p2 = "string 2";

    // push two different functions on to our stack to save them
    // for execution later.
    pushFunction ((void (*)(...))myFunc, "is", i, p);
    pushFunction ((void (*)(...))otherFunc, "iss", i, p, p2);

    // pop the function that is on the top of the stack and execute it.
    doFuncAndPop();

    // call a function that wants a function so that it will execute
    // the current function with its argument lists that is on top of the stack.
    mySignal (1, doFuncAndPop);

    return 0;
}

これで得られるもう1つの楽しみは、pushFunction()によって呼び出される関数内で関数を使用doFuncAndPop()して、引数を使用してスタックに配置できる別の関数を使用することです。

たとえば、otherFunc()上記のソースの関数を次のように変更した場合:

void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s\n", i, p, p2);
    pushFunction ((void (*)(...))myFunc, "is", i+2, p);
}

次に別の呼び出しを追加するdoFuncAndPop()と、最初otherFunc()に実行され、次に一時停止された呼び出しmyFunc()otherFunc()実行され、最後にmyFunc()にプッシュされた呼び出しmain ()が呼び出されます。

編集2: 次の関数を追加すると、スタックに配置されているすべての関数が実行されます。これにより、基本的に、関数と引数をスタックにプッシュしてから一連の関数呼び出しを実行することにより、小さなプログラムを作成できます。この関数を使用すると、引数なしで関数をプッシュしてから、いくつかの引数をプッシュすることもできます。スタックから関数をポップするときに、引数ブロックに有効な関数ポインターがない場合は、その引数リストをスタックの一番上にある引数ブロックに配置してから実行します。上記の関数にも同様の変更を加えることができますdoFuncAndPop()。そして、実行された関数でpushFunction()操作を使用すると、いくつかの興味深いことができます。

実際、これがスレッド型インタプリタの基礎になる可能性があります。

// execute all of the functions that have been pushed onto the stack.
void executeFuncStack () {
    if (myFunctionStackIndex > 0) {
        myFunctionStackIndex--;
        // if this item on the stack has a function pointer then execute it
        if (myFunctionStack[myFunctionStackIndex].f1) {
            myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
        } else if (myFunctionStackIndex > 0) {
            // if there is not a function pointer then assume that this is an argument list
            // for a function that has been pushed on the stack so lets execute the previous
            // pushed function with this argument list.
            int myPrevIndex = myFunctionStackIndex - 1;
            myFunctionStack[myPrevIndex].myArgList = myFunctionStack[myFunctionStackIndex].myArgList;
        }
        executeFuncStack();
    }
}

編集3: 次にpushFunc()、次の追加スイッチを使用してダブルを処理するように変更します。

case 'd':
  {
     double d;
     // double argument
     d = va_arg(argp, double);
     memcpy (pmyArgList, &d, sizeof(double));
     pmyArgList += sizeof(double);
   }
break;

したがって、この新しい関数を使用すると、次のようなことができます。まず、元の質問と同様の2つの関数を作成します。1つの関数内でpushFunction()を使用して引数をプッシュし、引数をスタック上の次の関数で使用します。

double f1 (double myDouble)
{
    printf ("f1 myDouble = %f\n", myDouble);
    return 0.0;
}

double g2 (double myDouble) {
    printf ("g2 myDouble = %f\n", myDouble);
    myDouble += 10.0;
    pushFunction (0, "d", myDouble);
    return myDouble;
}

新しい次の一連のステートメントで新しい機能を使用します。

double xDouble = 4.5;
pushFunction ((void (*)(...))f1, 0);
pushFunction ((void (*)(...))g2, "d", xDouble);
executeFuncStack();

これらのステートメントは、最初g2()に4.5の値を持つ関数を実行し、次に関数g2()はその戻り値をスタックにf1()プッシュして、最初にスタックにプッシュされた関数によって使用されます。

于 2012-10-20T22:18:09.650 に答える
4

未定義の動作に依存しようとしています。外部関数が終了したために内部関数がスコープから外れると、ポインターを介してその内部関数を呼び出す動作は未定義になります。何かが起こる可能性があります。整数の場合に誤って機能したという事実は、doubleに対して同じことを期待できること、または異なるコンパイラ、異なるコンパイラバージョン、異なるコンパイラフラグ、または異なるターゲットアーキテクチャに対してintに対して同じことを期待できることを意味するものではありません。

したがって、未定義の動作に依存しないでください。実際にそれらの警告に対して行動したときに、「警告に注意を払った」と主張しないでください。警告は明確に述べています:

含まれている関数が終了した後、そのアドレスを介してネストされた関数を呼び出そうとすると、すべての地獄が解き放たれます。

Cにはクロージャがないので、この意味でカリー化することはできません一部のデータを関数呼び出しに渡すと同様の効果が得られますが、それは通常の関数呼び出しとまったく同じようには見えないため、通常のカリー化のようには感じられません。C ++は、オブジェクトが構文的に関数のように動作できるため、柔軟性が高くなります。C ++の世界では、カリー化は通常、関数パラメーターの「<ahref="http://www.boost.org/doc/libs/1_51_0/libs/bind/bind.html"rel="nofollow">バインディング」と呼ばれます。 。

1つのコードが機能したが、他のコードが失敗した理由を本当に知りたい場合は、アセンブリコード(たとえばによって生成されたgcc -S -fverbose-asm)を取得し、頭の中で実行をシミュレートして、データなどに何が起こるかを確認できます。または、デバッガーを使用して、問題が発生した場所やデータの場所が変更された場所を確認することもできます。少し手間がかかるかもしれませんが、時間の価値があるとは思えません。

于 2012-10-20T19:11:29.973 に答える
0

取得できなかったので失礼しますが、とにかくコンパイル時に関数を宣言しているので、カレーの代わりにラップしてみませんか?カリー化の利点は、実行時に部分的に適用される関数を定義できることですが、ここではこれを行っていません。それとも私は何かが足りないのですか?

#include <stdio.h>

// Basic function summing its arguments
double g (double a, double b)
{
    return a+b;
}

double f1(double a)
{
    /* "1" can be replaced by a static initialized
       by another function, e.g.
       static double local_b = g(0, 1);
    */
    return g(a, 1);
}

int main () {
    printf ("(g(2))(1)=%f\n", f1(2));
}
于 2012-10-21T13:21:02.010 に答える
0

上記のコードの修正バージョン

#include <stdio.h>

typedef double (*function) (double,double);

// Basic function summing its arguments
double g (double a, double b)
{
        return a+b;
}

double f1(function wrapfunc,double a)
{
 /* "1" can be replaced by a static initialized
     by another function, e.g.
     static double local_b = g(0, 1);
 */
 return wrapfunc(a, 1);  
}

int main () {
        printf ("(g(2))(1)=%f\n", f1(g,2));
}

サンプルパラメータの例を使用して、さまざまな操作関数についてもう少し詳しく説明します。

#include<iostream>
#include<cstdio>

using namespace std;

#define N 4

#define LOOP(i) for(i=0; i<N; i++)

#define F(i) ( (int(*)(int,int))opt[i] )
#define FI F(i)
#define FJ F(j)
#define FK F(k)


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

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

u int mul(int a、int b){return a * b; }

int div(int a, int b) {
    if (b == 0 || a % b)
        return 2401;
    return a / b;
}

char whichOpt(int index)
{
    if (index == 0) return '+';
    else if (index == 1) return '-';
    else if (index == 2) return '*';
    return '/';
}

void howObtain24(int num[], void *opt[])
{
    int i, j, k, a, b, c, d;
    int ans=0;
    LOOP(i) LOOP(j) LOOP(k)
         LOOP(a) LOOP(b) LOOP(c) LOOP(d)
    {
        if (a == b || a == c || a == d || b == c || b == d || c == d)
            continue;
        if (FI(FJ(FK(num[a], num[b]), num[c]), num[d]) == 24) {
            std::cout << "((" << num[a] << whichOpt(k) << num[b] << ')'
                 << whichOpt(j) << num[c] << ')' << whichOpt(i) << num[d] << endl;
            ans++;
            continue;
        }
        if (FI(FJ(num[a], num[b]), FK(num[c], num[d])) == 24) {
            std::cout << '(' << num[a] << whichOpt(j) << num[b] << ')'
                 << whichOpt(i) << '(' << num[c] << whichOpt(k) << num[d] << ')' << endl;
            ans++;
            continue;
        }
    }
    if(ans==0)
    std::cout << "Non-Answer" << std::endl;
    return;

}

//=======================================================================
int main() {

    int num[N];

    void *opt[N] = { (void *)add, (void *)sub, (void *)mul, (void *)div };

    std::cout << "Input 4 Numbers between 1 and 10\n"
    for (int i = 0; i < N; i++)
        cin >> num[i];

    for (int j = 0; j < N; j++)
        if (num[j] < 1 || num[j] > 10) {
            std::cout << "InCorrect Input\n"

            return 0;
        }
        howObtain24(num, opt);

        return 0;
}
于 2013-09-16T06:05:15.897 に答える