Подтвердить что ты не робот

Есть ли способ добиться закрытия в 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();
}

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

Это было бы действительно потрясающе!

4b9b3361

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

В С++ 11 это будет достигнуто с помощью выражения lambda.

#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 = С++ 11.

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Мы приводим область 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