Есть ли способ добиться замыкания в C - PullRequest
32 голосов
/ 09 декабря 2010

Я бы хотел, чтобы это работало, но это не так:

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

На самом деле компилируется и работает для 1 экземпляра ... но второй отказывает. Любая идея, как получить замыкания в C?

Это было бы действительно здорово!

Ответы [ 6 ]

19 голосов
/ 09 декабря 2010

Использование 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 , которое работает так же, как замыкания.

7 голосов
/ 09 декабря 2010

GCC и clang имеют расширение блоков, которое по существу замыкается в C.

4 голосов
/ 01 января 2017

ANSI C не поддерживает закрытие и вложенные функции.Обходным путем для этого является использование простой "struct".

Простой пример закрытия для суммы двух чисел.

// 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
2 голосов
/ 14 марта 2018

Рабочее определение замыкания с примером JavaScript

Замыкание - это вид объекта, который содержит указатель или ссылку некоторого вида на функцию, которая должна быть выполнена вместе сэкземпляр данных, необходимых для функции.

Пример на JavaScript из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures -

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

Язык программирования C является статически типизированным языком, в отличие от JavaScript, не имеет сборки мусора и некоторых других функций, которые делают егопростое создание замыканий в JavaScript или других языках с внутренней поддержкой замыканий.

Одним из больших препятствий для замыканий в стандарте C является отсутствие языковой поддержки для типа конструкции в примере JavaScript, в котором замыкание не включаеттолько функция, а также копия данных, которые собираются при создании замыкания, способ сохранения состояния, который затем может бытьиспользуется, когда замыкание выполняется вместе с любыми дополнительными аргументами, предоставляемыми в то время, когда вызывается функция замыкания.

Однако C имеет некоторые базовые строительные блоки, которые могут предоставить инструменты для создания своего рода замыкания.Некоторые из трудностей заключаются в том, что (1) управление памятью является обязанностью программиста, нет сборки мусора, (2) функции и данные разделены, нет классов или механики типов классов, (3) статически типизированы, поэтому не требуется обнаружение типов данных во время выполненияили размерами данных, и (4) плохими языковыми возможностями для захвата данных о состоянии во время создания замыкания.

Одна вещь, которая делает возможной функцию замыкания с помощью C, это указатель void * и использование unsigned char как тип памяти общего назначения, который затем преобразуется в другие типы посредством приведения.

Реализация со стандартом C и немного растяжения здесь и там

ПРИМЕЧАНИЕ: Следующий пример зависит от соглашения о передаче аргументов на основе стека, которое используется в большинстве 32-разрядных компиляторов x86.Большинство компиляторов также позволяют указывать соглашение о вызовах, отличное от передачи аргументов на основе стека, например, модификатор __fastcall в Visual Studio.По умолчанию для 64-битной и 64-битной Visual Studio используется соглашение __fastcall по умолчанию, чтобы аргументы функции передавались в регистрах, а не в стеке.См. Обзор соглашений о вызовах x64 в Microsoft MSDN, а также Как установить аргументы функций в сборке во время выполнения в 64-битном приложении в Windows? , а также различные ответы и комментарии в Как переменные аргументы реализованы в gcc? .

Одна вещь, которую мы можем сделать, - решить эту проблему, предоставив некоторую возможность закрытия для C, - это упростить проблему.Лучше обеспечить 80% -ное решение, которое полезно для большинства приложений, чем вообще никакого решения.

Одним из таких упрощений является поддержка только тех функций, которые не возвращают значение, другими словами, функции, объявленные как void func_name().Мы также собираемся отказаться от проверки типа времени компиляции списка аргументов функции, так как этот подход создает список аргументов функции во время выполнения.Ни одна из этих вещей, от которых мы отказываемся, не тривиальна, поэтому вопрос в том, перевешивает ли ценность этого подхода для замыканий в C то, что мы отказываемся.

Прежде всего, давайте определим нашу область данных замыканий.Область данных замыкания представляет собой область памяти, которую мы будем использовать для хранения информации, необходимой для замыкания.Минимальный объем данных, о котором я могу думать, - это указатель на функцию, которую нужно выполнить, и копия данных, которые будут предоставлены функции в качестве аргументов.

В этом случае мы собираемся предоставить любые захваченные данные о состояниинужен функции в качестве аргумента функции.

Мы также хотим иметь некоторые базовые средства безопасности, чтобы мы могли достаточно безопасно потерпеть неудачу.К сожалению, рельсы безопасности немного слабы, так как некоторые обходные пути, которые мы используем для реализации формы замыканий.

Исходный код

Следующий исходный код былразработано с использованием 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() функция для выполнения функции замыкания с ее данными.В этой функции мы копируем список аргументов, предоставленный в структуре данных замыкания, в стек при вызове функции.

Мы приводим область аргументов данных замыкания к указателю наструктура, содержащая массив 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 голосов
/ 09 декабря 2010

GCC поддерживает внутренние функции, но не замыкания.C ++ 0x будет иметь замыкания.Ни одна версия C, о которой я знаю, и, конечно, ни одна стандартная версия, не обеспечивает такой уровень удивительности.

Phoenix , который является частью Boost, обеспечивает замыкания в C ++.

1 голос
/ 21 июня 2014

На этой странице вы можете найти описание того, как делать замыкания в C:

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

Идея состоит в том, что структура необходима, и эта структура содержит указатель на функцию, но предоставляется функции в качестве первого аргумента.Помимо того, что для этого требуется много кода, и управление памятью само собой разумеется, это работает и обеспечивает мощь и возможности закрытия других языков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...