Is there a a way to achieve closures in C

2019-01-08 13:06发布

问题:

I would like this to work, but it does not:

#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();
}

It actually does compile and does work for 1 instance ... but the second one fails. Any idea how to get closures in C?

It would be truly awesome!

回答1:

Using 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);
}

But usually in C you end up passing extra arguments around to fake closures.


Apple has a non-standard extension to C called blocks, which do work much like closures.



回答2:

GCC and clang have the blocks extension, which is essentially closures in C.



回答3:

The ANSI C has not a support for closure, as well as nested functions. Workaround for it is usage simple "struct".

Simple example closure for sum two numbers.

// 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;
}

Usage:

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));
}

Result:

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

On C++11 it will be achived by use lambda expression.

#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);
}

A result, after compilation with a flag -std=c++11.

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


回答4:

GCC supports inner functions, but not closures. C++0x will have closures. No version of C that I'm aware of, and certainly no standard version, provides that level of awesome.

Phoenix, which is part of Boost, provides closures in C++.



回答5:

On this page you can find a description on how to do closures in C:

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

The idea is that a struct is needed and that struct contains the function pointer, but gets provided to the function as first argument. Apart from the fact that it requires a lot of boiler plate code and the memory management is off course an issue, this works and provides the power and possibilities of other languages' closures.



回答6:

A Working Definition of a Closure with a JavaScript Example

A closure is a kind of object that contains a pointer or reference of some kind to a function to be executed along with the an instance of the data needed by the function.

An example in JavaScript from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures is

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
  };
}

which could then be used like:

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

Some of the Obstacles to Overcome with C

The C programming language is a statically typed language, unlike JavaScript, nor does it have garbage collection, and some other features that make it easy to do closures in JavaScript or other languages with intrinsic support for closures.

One large obstacle for closures in Standard C is the lack of language support for the kind of construct in the JavaScript example in which the closure includes not only the function but also a copy of data that is captured when the closure is created, a way of saving state which can then be used when the closure is executed along with any additional arguments provided at the time the closure function is invoked.

However C does have some basic building blocks which can provide the tools for creating a kind of closure. Some of the difficulties are (1) memory management is the duty of the programmer, no garbage collection, (2) functions and data are separated, no classes or class type mechanics, (3) statically typed so no run time discovery of data types or data sizes, and (4) poor language facilities for capturing state data at the time the closure is created.

One thing that makes something of a closure facility possible with C is the void * pointer and using unsigned char as a kind of general purpose memory type which is then transformed into other types through casting.

An Implementation With Standard C and a Bit of Stretching Here and There

NOTE: The following example depends on a stack based argument passing convention as is used with most x86 32 bit compilers. Most compilers also allow for a calling convention to be specified other than stack based argument passing such as the __fastcall modifier of Visual Studio. The default for x64 and 64 bit Visual Studio is to use the __fastcall convention by default so that function arguments are passed in registers and not on the stack. See Overview of x64 Calling Conventions in the Microsoft MSDN as well as How to set function arguments in assembly during runtime in a 64bit application on Windows? as well as the various answers and comments in How are variable arguments implemented in gcc? .

One thing that we can do is to solve this problem of providing some kind of closure facility for C is to simplify the problem. Better to provide an 80% solution that is useful for a majority of applications than no solution at all.

One such simplification is to only support functions that do not return a value, in other words functions declared as void func_name(). We are also going to give up compile time type checking of the function argument list since this approach builds the function argument list at run time. Neither one of these things that we are giving up are trivial so the question is whether the value of this approach to closures in C outweighs what we are giving up.

First of all lets define our closure data area. The closure data area represents the memory area we are going to use to contain the information we need for a closure. The minimum amount of data I can think of is a pointer to the function to execute and a copy of the data to be provided to the function as arguments.

In this case we are going to provide any captured state data needed by the function as an argument to the function.

We also want to have some basic safe guards in place so that we will fail reasonably safely. Unfortunately the safety rails are a bit weak with some of the work arounds we are using to implement a form of closures.

The Source Code

The following source code was developed using Visual Studio 2017 Community Edition in a .c C source file.

The data area is a struct that contains some management data, a pointer to the function, and an open ended data area.

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;

Next we create a function that will initialize a closure data area.

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;
}

This function is designed to accept a pointer to a data area which gives flexibility as to how the user of the function wants to manage memory. They can either use some memory on the stack or static memory or they can use heap memory via the malloc() function.

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

or

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

Next we provide a function that allows us to add data and arguments to our closure. The purpose of this function is to build up the closure data so that when closure function is invoked, the closure function will be provided any data it needs to do its job.

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;
}

And to make this a bit simpler to use lets provide a wrapping macro which is generally handy but does have limitations since it is C Processor text manipulation.

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

so we could then use something like the following source code:

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

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

Invoking the Closure: The execClosure() Function

The last piece to this is the execClosure() function to execute the closure function with its data. What we are doing in this function is to copy the argument list supplied in the closure data structure onto the stack as we invoke the function.

What we do is cast the args area of the closure data to a pointer to a struct containing an unsigned char array and then dereference the pointer so that the C compiler will put a copy of the arguments onto the stack before it calls the function in the closure.

To make it easier to create the execClosure() function, we will create a macro that makes it easy to create the various sizes of structs we need.

// 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);\
}

Then we use this macro to create a series of tests to determine how to call the closure function. The sizes chosen here may need tweaking for particular applications. These sizes are arbitrary and since the closure data will rarely be of the same size, this is not efficiently using stack space. And there is the possibility that there may be more closure data than we have allowed for.

// 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;
}

We return the pointer to the closure in order to make it easily available.

An Example Using the Library Developed

We can use the above as follows. First a couple of example functions that don't really do much.

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;
}

Next we build our closures and execute them.

{
    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);
}

Which gives an output of

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

Well What About Currying?

Next we could make a modification to our closure struct to allow us to do currying of functions.

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;

with the supporting functions for currying and resetting of a curry point being

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

The source code for testing this could be:

{
    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)));
}

with the output of

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


标签: c closures