关于gcc:C中的函数式编程(固化)/类型问题

Functional Programming (Currying) in C / Issue with Types

作为染上羊毛的函数式程序员,我发现很难不尝试将自己喜欢的范例塞进我正在使用的任何语言中。在编写C时,我发现我想使用我的函数之一,然后传递部分应用的函数。阅读后,有什么方法可以在C语言中进行计算?并注意我提出的http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions中的警告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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
"
,f1(g(2)));
}

哪个按预期运行。但是,我的原始程序可用于double s,因此我认为我只需要更改适当的类型就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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
"
,f1(g(2)));
}

这将产生如下内容:

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

错误的选择似乎是随机的。此外,如果使用-O3编译任一示例,则编译器本身都会抛出Segmentation fault: 11。我在任何时候都没有收到来自gcc的警告,而且我无法弄清楚发生了什么。有谁知道为什么第二个程序失败而第一个程序没有失败?还是更好地解决第二个问题?

我的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

编辑:明确地说,我了解我要做的是愚蠢的。此代码不会在"好奇号"流动站或NYSE上运行。我试图更多地了解(GNU)C中的函数指针如何工作,并解释一些有趣的发现。我保证在现实世界中永远不会做这样的事情。


一个有趣的问题,我看了引用的答案中的论文(具有Curried函数的C / C ++ / Objective-C具有更高的函数可重用性)。

好。

因此,以下是您可能要去的建议路径。我认为这不是真正的Curried函数,因为我不完全理解本文所讲的内容,而不是作为函数式程序员。但是,通过做一些工作,我发现其中一些有趣的应用。另一方面,我不确定这是否是您想要的,这让我震惊,您可以在C中完成此操作。

好。

似乎有两个问题。

好。

首先是能够处理带有任意参数列表的任意函数调用。我采用的方法是使用标准C库变量参数功能(带有va_start(),va_arg()和va_end()函数的va_list),然后将函数指针与提供的参数一起存储到数据区域中,以便它们然后可以在以后的时间执行。我借用并修改了printf()函数如何使用格式行来了解提供了多少个参数及其类型。

好。

接下来是函数及其参数列表的存储。我只是使用一个任意大小的结构来尝试这个概念。这将需要更多的思考。

好。

此特定版本使用的数组被视为堆栈。有一个函数,您可以使用它的参数将任意函数推入堆栈数组,还有一个函数会将最上面的函数及其参数从堆栈数组中弹出并执行。

好。

但是,实际上您可以在某种集合中具有任意的struct对象,例如哈希映射,这可能非常酷。

好。

我只是从论文中借用了信号处理程序示例,以表明该概念适用于此类应用程序。

好。

所以这是源代码,希望它能帮助您提出解决方案。

好。

您将需要向开关添加其他情况,以便能够处理其他参数类型。我只是做了一些概念验证。

好。

尽管表面上看起来是相当简单的扩展,但这也不执行调用函数的函数。就像我说的,我并没有完全理解Curried。

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#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
"
, i, p);
}

void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s
"
, 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;
}

您可以从中获得的另一点乐趣是,在doFuncAndPop()调用的函数中使用pushFunction()函数,可以将另一个函数及其参数放入堆栈中。

好。

例如,如果您在上面的源代码中将函数otherFunc()修改为如下所示:

好。

1
2
3
4
5
6
void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s
"
, i, p, p2);
    pushFunction ((void (*)(...))myFunc,"is", i+2, p);
}

如果您随后向doFuncAndPop()添加另一个调用,则会看到首先执行了otherFunc(),然后执行了对otherFunc()中使用的对myFunc()的调用,最后执行了在myFunc()中推送的myFunc()调用。 x9>被调用。

好。

编辑2:
如果我们添加以下函数,它将执行所有已放入堆栈的函数。通过将函数和参数推入堆栈,然后执行一系列函数调用,这将使我们能够从根本上创建一个小程序。此函数还将允许我们推送没有任何参数的函数,然后推送一些参数。当将函数弹出堆栈时,如果参数块没有有效的函数指针,那么我们要做的就是将该参数列表放到堆栈顶部的参数块上,然后执行该函数。也可以对上面的函数doFuncAndPop()进行类似的更改。而且,如果我们在执行的函数中使用pushFunction()操作,我们可以做一些有趣的事情。

好。

实际上,这可能是线程翻译的基础。

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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()进行更改,以使用以下附加开关来处理double:

好。

1
2
3
4
5
6
7
8
9
case 'd':
  {
     double d;
     // double argument
     d = va_arg(argp, double);
     memcpy (pmyArgList, &d, sizeof(double));
     pmyArgList += sizeof(double);
   }
break;

因此,使用此新功能,我们可以执行以下操作。首先创建类似于原始问题的两个函数。我们将在一个函数中使用pushFunction()来推送参数,然后该参数将由堆栈上的下一个函数使用。

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
double f1 (double myDouble)
{
    printf ("f1 myDouble = %f
"
, myDouble);
    return 0.0;
}

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

新增功能我们将新功能与以下一系列语句结合使用:

好。

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

这些语句将首先执行值为4.5的函数g2(),然后函数g2()将其返回值压入我们的堆栈,以供首先被压入堆栈的函数f1()使用。

好。

好。


您试图依赖未定义的行为:内部函数超出范围,因为外部函数确实退出了,通过某个指针调用该内部函数的行为是不确定的。可能发生任何事情。对于整数情况意外地起作用的事实并不意味着您可以期望对double使用相同的东西,甚至对于不同的编译器,不同的编译器版本,不同的编译器标志或不同的目标体系结构,对于int都可以期望相同。

因此,不要依赖不确定的行为。实际上,您针对这些警告采取了行动时,请不要声称自己已"听取了这些警告"。该警告明确指出:

If you try to call the nested function through its address after the containing function has exited, all hell will break loose.

C中没有闭包,因此从这种意义上讲,不会有任何麻烦。如果将一些数据传递给函数调用,则可以得到类似的效果,但是它看起来不像正常的函数调用,因此不会像普通的柯里化那样。 C ++在那里具有更大的灵活性,因为它允许对象在语法上类似于函数。在C ++世界中,通常将currying称为函数参数的"绑定"。

如果您真的想知道为什么一段代码可以工作,而另一段代码却失败,则可以采用汇编代码(例如由gcc -S -fverbose-asm生成)并在脑海中模拟执行,以查看数据和内容发生了什么。或者,您可以使用调试器查看失败的地方或数据位置发生变化。可能需要做一些工作,我怀疑是否值得。


上面代码的固定版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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
"
, f1(g,2));
}

有关示例参数示例的其他操作功能的更多信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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){返回a * b; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
"

    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
"


            return 0;
        }
        howObtain24(num, opt);

        return 0;
}

请原谅我没有得到它,但是为什么不包装而不是咖喱呢,因为无论如何您都是在编译时声明函数的? currying的优点是-或至少在我看来-您可以在运行时定义部分应用的函数,但是在这里您无需这样做。还是我错过了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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
"
, f1(2));
}