sourcetip

C에서 폐쇄를 달성할 수 있는 방법이 있습니까?

fileupload 2023. 6. 12. 21:51
반응형

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

실제로 컴파일을 수행하고 한 인스턴스에 대해 작동하지만 두 번째 인스턴스는 실패합니다.C에서 어떻게 문을 닫는지 아십니까?

정말 멋질 거예요!

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(&incrementer_, &in1);
    int (*emitter1)() = alloc_callback(&emitter_, &in1);
    int (*incrementer2)() = alloc_callback(&incrementer_, &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에 대한 비표준 확장 기능을 가지고 있습니다.

ANSIC에는 중첩 함수뿐만 아니라 폐쇄도 지원되지 않습니다.이에 대한 해결 방법은 간단한 "구조"를 사용하는 것입니다.

두 숫자의 합에 대한 간단한 예제 마감입니다.

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

자바스크립트를 이용한 폐쇄의 작업 정의 예제

폐쇄는 함수에 필요한 데이터 인스턴스와 함께 실행할 함수에 대한 포인터 또는 참조를 포함하는 개체의 일종입니다.

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 프로그래밍 언어는 자바스크립트와 달리 정적으로 형식화된 언어이며 가비지 컬렉션이 없으며 폐쇄에 대한 본질적인 지원이 있는 자바스크립트 또는 다른 언어에서 폐쇄를 쉽게 수행할 수 있는 몇 가지 다른 기능도 있습니다.

표준 C에서 폐쇄의 한 가지 큰 장애물은 폐쇄가 함수뿐만 아니라 폐쇄가 생성될 때 캡처되는 데이터의 복사본을 포함하는 자바스크립트 예제에서 구조의 종류에 대한 언어 지원이 부족하다는 것입니다.폐쇄 함수가 호출될 때 제공된 추가 인수와 함께 폐쇄가 실행될 때 사용할 수 있는 상태 저장 방법입니다.

그러나 C에는 일종의 폐쇄를 만드는 도구를 제공할 수 있는 몇 가지 기본 구성 요소가 있습니다.몇 가지 어려움은 (1) 메모리 관리가 프로그래머의 의무이고, 가비지 수집이 없으며, (2) 함수와 데이터가 분리되어 있고, 클래스나 클래스 유형 메커니즘이 없으며, (3) 정적으로 입력되어 데이터 유형이나 데이터 크기의 런타임 검색이 없고, (4) 폐쇄가 생성될 때 상태 데이터를 캡처하기 위한 언어 기능이 부족하다는 것입니다.

C로 폐쇄 시설의 어떤 것을 가능하게 하는 한 가지입니다.void *unsigned char일종의 범용 메모리 유형으로서 주조를 통해 다른 유형으로 변형됩니다.

새로운 접근 방식으로 업데이트

저의 원래 게시된 답변은 사람들이 그것을 지지할 정도로 충분히 도움이 된 것으로 보이지만 제가 좋아하지 않는 제약이 한두 개 있었습니다.

최근의 투표에 대한 알림을 받고, 저는 게시된 다른 답변들 중 일부를 살펴보았고, 저를 괴롭히는 문제를 극복할 수 있는 두 번째 접근법을 제공할 수 있다는 것을 깨달았습니다.

기존 접근 방식의 문제를 제거하는 새로운 접근 방식

원래 접근 방식에서는 스택에 함수 인수를 전달해야 했습니다.이 새로운 접근 방식은 이러한 요구사항을 제거합니다.그것은 또한 훨씬 더 깨끗해 보입니다.저는 아래의 원래 접근 방식을 유지하고 있습니다.

새로운 접근 방식은 단일 솔루션을 사용합니다.struct,ClosureStruct하는 두 폐를구축기위두가기함께능과지쇄하한▁along,,makeClosure()그리고.pushClosureArg().

은 또한 이새운접또변한인의 합니다.stdarg.h폐쇄 데이터에서 캡처된 인수를 처리합니다.

C 소스 코드 파일에서 다음을 사용하려면 다음이 필요합니다.

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <stdarg.h>


typedef struct {
    void (*p)();            // pointer to the function of this closure
    size_t  sargs;          // size of the memory area allocated for closure data
    size_t  cargs;          // current memory area in use for closure data
    unsigned char * args;   // pointer to the allocated closure data area
} ClosureStruct;

void * makeClosure(void (*p)(), size_t sargs)
{
    // allocate the space for the closure management data and the closure data itself.
    // we do this with a single call to calloc() so that we have only one pointer to
    // manage.
    ClosureStruct* cp = calloc(1, sizeof(ClosureStruct) + sargs);

    if (cp) {
        cp->p = p;             // save a pointer to the function
        cp->sargs = sargs;     // save the total size of the memory allocated for closure data
        cp->cargs = 0;         // initialize the amount of memory used
        cp->args = (unsigned char *)(cp + 1);  // closure data is after closure management block
    }

    return cp;
}

void * pushClosureArg(void* cp, size_t sarg, void* arg)
{
    if (cp) {
        ClosureStruct* p = cp;
        if (p->cargs + sarg <= p->sargs) {
            // there is room in the closure area for this argument so make a copy
            // of the argument and remember our new end of memory.
            memcpy(p->args + p->cargs, arg, sarg);
            p->cargs += sarg;
        }
    }

    return cp;
}

이 코드는 다음과 유사하게 사용됩니다.

// example functions that we will use with closures

// funcadd() is a function that accepts a closure with two int arguments
// along with three additional int arguments.
// it is similar to the following function declaration:
//  void funcadd(int x1, int x2, int a, int b, int c);
//
void funcadd(ClosureStruct* cp, int a, int b, int c)
{
    // using the variable argument functionality we will set our
    // variable argument list address to the closure argument memory area
    // and then start pulling off the arguments that are provided by the closure.
    va_list jj;
    va_start(jj, cp->args);    // get the address of the first argument

    int x1 = va_arg(jj, int);    // get the first argument of the closure
    int x2 = va_arg(jj, int);

    printf("funcadd() = %d\n", a + b + c + x1 + x2);
}

int zFunc(ClosureStruct* cp, int j, int k)
{
    va_list jj;

    va_start(jj, cp->args);    // get the address of the first argument
    int i = va_arg(jj, int);

    printf("zFunc() i = %d, j = %d, k = %d\n", i, j, k);
    return i + j + k;
}

typedef struct { char xx[24]; } thing1;

int z2func( ClosureStruct* cp, int i)
{
    va_list jj;

    va_start(jj, cp->args);    // get the address of the first argument
    thing1 a = va_arg(jj, thing1);

    printf("z2func() i = %d, %s\n", i, a.xx);
    return 0;
}

int mainxx(void)
{
    ClosureStruct* p;

    int x;
    thing1 xpxp = { "1234567890123" };

    p = makeClosure(funcadd, 256);
    x = 4;  pushClosureArg(p, sizeof(int), &x);
    x = 10;  pushClosureArg(p, sizeof(int), &x);

    p->p(p, 1, 2, 3);

    free(p);

    p = makeClosure(z2func, sizeof(thing1));
    pushClosureArg(p, sizeof(thing1), &xpxp);

    p->p(p, 45);
    free(p);

    p = makeClosure(zFunc, sizeof(int));
    x = 5; pushClosureArg(p, sizeof(int), &x);

    p->p(p, 12, 7);

    return 0;
}

위의 사용 결과는 다음과 같습니다.

funcadd() = 20
z2func() i = 45, 1234567890123
zFunc() i = 5, j = 12, k = 7

그러나 위의 구현에는 문제가 있지만 값을 반환하는 함수의 반환 값을 가져올 방법이 없습니다. 다말해, 함는수입니다.zFunc()위의 종결에서 사용된 것은 반환합니다.int무시되는 값입니다.▁like하▁with면▁value▁▁return▁if▁the▁.int k = pint->p(pint, 12, 7); pointer가 function pointer로 지정되어 .ClosureStruct이라void (*p)();int (*p)();.

제한을 두 의 C 하여 C 프리프로세서 매크로를 입니다.ClosureStruct는 " " 다이의반유지구는조"가 아닌void.

#define NAME_CLOSURE(t) ClosureStruct_ ## t

#define DEF_CLOSURE(t) \
typedef struct {      \
t (*p)();             \
size_t  sargs;        \
size_t  cargs;        \
unsigned char* args;  \
} NAME_CLOSURE(t);

두 그런다음두가기재다니정합의능을지▁the,▁functions다를 재정의합니다.zFunc()그리고.z2func()다음과 같이 매크로를 사용합니다.

DEF_CLOSURE(int)      // define closure struct that returns an int

int zFunc(NAME_CLOSURE(int)* cp, int j, int k)
{
    va_list jj;

    va_start(jj, cp->args);    // get the address of the first argument
    int i = va_arg(jj, int);

    printf("zFunc() i = %d, j = %d, k = %d\n", i, j, k);
    return i + j + k;
}

typedef struct { char xx[24]; } thing1;

int z2func( NAME_CLOSURE(int) * cp, int i)
{
    va_list jj;

    va_start(jj, cp->args);    // get the address of the first argument
    thing1 a = va_arg(jj, thing1);

    printf("z2func() i = %d, %s\n", i, a.xx);
    return 0;
}

이를 다음과 같이 사용합니다.

int mainxx(void)
{
    ClosureStruct* p;
    NAME_CLOSURE(int) *pint;

    int x;
    thing1 xpxp = { "1234567890123" };

    p = makeClosure(funcadd, 256);
    x = 4;  pushClosureArg(p, sizeof(int), &x);
    x = 10;  pushClosureArg(p, sizeof(int), &x);

    p->p(p, 1, 2, 3);

    free(p);

    pint = makeClosure(z2func, sizeof(thing1));
    pushClosureArg(pint, sizeof(thing1), &xpxp);

    int k = pint->p(pint, 45);

    free(pint);

    pint = makeClosure(zFunc, sizeof(int));
    x = 5; pushClosureArg(pint, sizeof(int), &x);

    k = pint->p(pint, 12, 7);
    return 0;
}

표준 C와 약간의 스트레칭을 사용한 첫 번째 구현

참고: 다음 예제는 대부분의 x86 32비트 컴파일러에서 사용되는 스택 기반 인수 전달 규칙에 따라 달라집니다.의 컴파일러에서는 "" " " " " " " " " 와 같은 할 수 .__fastcallVisual Studio 수식입다니어의.및은 x64 X64 Visual Studio를 사용하는 것입니다.__fastcall함수 인수가 스택이 아닌 레지스터에서 전달되도록 기본적으로 규약을 지정합니다.Microsoft MSDN의 x64 호출 규칙 개요 Windows의 64비트 응용 프로그램에서 런타임 동안 어셈블리에서 함수 인수를 설정하는 방법참조하십시오.변수 인수는 gcc에서 어떻게 구현됩니까?의 다양한 답변과 주석뿐 아니라.

우리가 할 수 있는 한 가지는 C에게 일종의 폐쇄 시설을 제공하는 이 문제를 해결하는 것입니다.솔루션을 전혀 제공하지 않는 것보다 대부분의 애플리케이션에 유용한 80% 솔루션을 제공하는 것이 좋습니다.

이러한 단순화 중 하나는 값을 반환하지 않는 함수, 즉 다음과 같이 선언된 함수만 지원하는 것입니다.void func_name()또한 이 접근 방식은 런타임에 함수 인수 목록을 빌드하기 때문에 함수 인수 목록의 컴파일 시간 유형 검사를 포기할 것입니다.우리가 포기하는 이러한 것들 중 어느 것도 사소한 것이 아니기 때문에 문제는 C에서 폐쇄에 대한 이 접근법의 가치가 우리가 포기하는 것보다 더 큰지 여부입니다.

우선 폐쇄 데이터 영역을 정의합니다.폐쇄 데이터 영역은 폐쇄에 필요한 정보를 포함하는 데 사용할 메모리 영역을 나타냅니다.제가 생각할 수 있는 최소한의 데이터 양은 실행할 함수에 대한 포인터와 함수에 인수로 제공될 데이터의 복사본입니다.

이 경우 함수에 필요한 캡처된 상태 데이터를 함수에 대한 인수로 제공합니다.

우리는 또한 합리적으로 안전하게 실패할 수 있도록 기본적인 안전장치를 마련하기를 원합니다.안타깝게도 안전 레일은 폐쇄 형태를 구현하기 위해 사용하는 일부 작업으로 인해 약간 약합니다.

소스 코드

다음 소스 코드는 .cC 소스 파일에서 Visual Studio 2017 Community Edition을 사용하여 개발되었습니다.

데이터 영역은 일부 관리 데이터, 함수 포인터 및 개방형 데이터 영역을 포함하는 구조입니다.

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

종료 호출:exec Closure() 함수

이것에 대한 마지막 조각은.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

GCC와 clang은 블록 확장을 가지고 있으며, 이는 본질적으로 C에서 폐쇄입니다.

GCC는 내부 기능을 지원하지만 폐쇄는 지원하지 않습니다.C++0x는 닫힙니다.제가 아는 어떤 버전의 C도, 그리고 확실히 어떤 표준 버전도 그 정도의 놀라운 수준을 제공하지 않습니다.

Boost의 일부인 Phoenix는 C++로 폐쇄를 제공합니다.

이 페이지에서 C:에서 폐쇄를 수행하는 방법에 대한 설명을 찾을 수 있습니다.

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

이 개념은 구조체가 필요하고 그 구조체가 함수 포인터를 포함하지만 함수에 첫 번째 인수로 제공된다는 것입니다.보일러 플레이트 코드가 많이 필요하고 메모리 관리가 코스 밖에서 문제가 되는 것 외에도, 이것은 다른 언어의 폐쇄의 힘과 가능성을 제공하고 작동합니다.

에서 이를 수행하는 관용적인 방법은 C가 컨텍스트에 함수 포인터와 보이드 포인터를 전달하는 것입니다.

하지만, 얼마 전에 저는 다른 접근법을 생각해냈습니다.놀랍게도, C에는 데이터와 코드 자체를 모두 전달하는 내장형 유형 제품군이 있습니다.이것들은 함수 포인터에 대한 포인터입니다.

이 방법은 이 단일 개체를 사용하여 함수 포인터를 다시 참조하여 두 코드를 모두 전달하는 것입니다.다음으로 첫 번째 인수로 컨텍스트와 동일한 이중 함수 포인터를 전달합니다.그것은 실제로 매우 유연하고 읽을 수 있는 기계적인 폐쇄를 초래하기 때문에 약간 복잡해 보입니다.

코드 참조:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// typedefing functions makes usually makes code more readable
typedef double double_fun_t(void*, double);

struct exponential {
   // closure must be placed as the first member to allow safe casting
   // between a pointer to `closure` and `struct exponential`
  double_fun_t *closure;
  double temperature;
};

double exponential(void *ctx_, double x) {
  struct exponential *ctx = ctx_;
  return exp(x / ctx->temperature);
}

// the "constructor" of the closure for exponential
double_fun_t **make_exponential(double temperature) {
  struct exponential *e = malloc(sizeof *e);
  e->closure = exponential;
  e->temperature = temperature;
  return &e->closure;
}

// now simple closure with no context, a pure x -> x*x mapping
double square(void *_unused, double x){
    (void)_unused;
    return x*x;
}

// use compound literal to transform a function to a closure
double_fun_t **square_closure = & (double_fun_t*) { square };

// the worker that process closures, note that `double_fun_t` is not used
// because `double(**)(void*,double)` is builtin type
double somme(double* liste, int length, double (**fun)(void*,double)){
    double poids = 0;
    for(int i=0;i<length;++i)
        // calling a closure, note that `fun` is used for both obtaing
        // the function pointer and for passing the context
        poids = poids + (*fun)(fun, liste[i]);
    return poids;
}

int main(void) {
    double list[3] = { 1, 2, 3 };
    
    printf("%g\n", somme(list, 3, square_closure));

    // a dynamic closure
    double_fun_t **exponential = make_exponential(42);
    printf("%g\n", somme(list, 3, exponential));
    free(exponential);

    return 0;
}

이 접근 방식의 장점은 폐쇄가 이중 > 이중 함수를 호출하기 위한 순수한 인터페이스를 내보낸다는 것입니다.폐점의 모든 고객이 사용하는 복싱 구조물을 도입할 필요는 없습니다.유일한 요구 사항은 매우 자연스럽고 코드를 공유할 필요가 없는 "호출 규칙"입니다.

다음을 통해 이를 달성할 수 있습니다.-fblocks 하지만 , 하만지그보멋않져이습다는니지렇게럼나▁in▁j,▁flag▁so▁it않▁nice▁but▁look다▁like▁does습니s▁not▁ts,s.

#include <stdio.h>
#include <stdlib.h>
#include <Block.h>

#define NEW(T) ({ \
    T* __ret = (T*)calloc(1, sizeof(T)); \
    __ret; \
})

typedef struct data_t {
    int value;
} data_t;

typedef struct object_t {
    int (^get)(void);
    void (^set)(int);
    void (^free)(void);
} object_t;

object_t const* object_create(void) {
    data_t* priv = NEW(data_t);
    object_t* pub = NEW(object_t);

    priv->value = 123;

    pub->get = Block_copy(^{
        return priv->value;
    });

    pub->set = Block_copy(^(int value){
        priv->value = value;
    });

    pub->free = Block_copy(^{
        free(priv);
        free(pub);
    });

    return pub;
}

int main() {
    object_t const* obj = object_create();
    printf("before: %d\n", obj->get());
    obj->set(321);
    printf("after: %d\n", obj->get());
    obj->free();
    return 0;
}
clang main.c -o main.o -fblocks -fsanitize=address; ./main.o
before: 123
after: 321

정답.

#include <stdio.h>
#include <stdlib.h>

/*
File Conventions
----------------
alignment: similar statements only
           int    a = 10;
           int* omg = {120, 5};
functions: dofunction(a, b, c);
macros:    _do_macro(a, b, c);
variables: int dovariable=10;
*/


////Macros

#define _assert(got, expected, teardownmacro) \
  do { \
    if((got)!=(expected)) { \
      fprintf(stderr, "line %i: ", __LINE__); \
      fprintf(stderr, "%i != %i\n", (got), (expected)); \
      teardownmacro; \
      return EXIT_FAILURE; \
    } \
  } while(0);


////Internal Helpers

static void istarted() {
  fprintf(stderr, "Start tests\n");
}

static void iended() {
  fprintf(stderr, "End tests\n");
}

////Tests

int main(void)
{
  ///Environment

  int  localvar = 0;
  int* localptr = NULL;


  ///Closures

#define _setup_test(mvar, msize) \
  do { \
    localptr=calloc((msize), sizeof(int)); \
    localvar=(mvar); \
  } while(0);

#define _teardown_test() \
  do { \
    free(localptr); \
    localptr=NULL; \
  } while(0);

  
  ///Tests
  
  istarted();

  _setup_test(10, 2);
  _assert(localvar, 10, _teardown_test());
  _teardown_test();

  _setup_test(100, 5);
  _assert(localvar, 100, _teardown_test());
  _teardown_test();

  iended();

  return EXIT_SUCCESS;
}

맥락

저는 C에서 다른 사람들이 어떻게 이것을 성취했는지 궁금했습니다.저는 이 답을 보지 못했을 때 완전히 놀라지 않았습니다.경고: 이 답은 초보자를 위한 것이 아닙니다.

저는 유닉스 스타일의 사고방식으로 훨씬 더 많이 살고 있습니다. 제 개인 프로그램과 라이브러리는 작고 한 가지 일을 매우 잘 합니다.이러한 맥락에서 "폐쇄" 매크로가 훨씬 안전합니다.는 가독성을 위한 모든 조직과 지정된 규칙이 매우 중요하다고 생각합니다. 그래서 코드는 나중에 우리가 읽을 수 있고, 매크로는 매크로처럼 보이고 함수는 함수처럼 보입니다.다른 언어 구조(매크로 및 함수)를 구별하기 위해 지정되고 따르는 이러한 개인적인 관례가 아니라 단지 일부를 갖는 것을 명확히 하기 위해.어쨌든 우리 모두는 그렇게 해야 합니다.

매크로를 두려워하지 마십시오.그것이 타당할 때: 그것들을 사용하세요. 고급 부분은 시기입니다.저의 예는 언제의 한 예입니다.그들은 터무니없이 강력하고 그렇게 무섭지 않습니다.


횡설수설

함수 내에서 일련의 식을 반복적으로 실행하기 위해 다른 언어에서 적절한 폐쇄/람다를 사용하기도 합니다.이것은 약간의 상황 인식 개인 도우미 기능입니다.적절한 정의와 상관없이, 그것은 폐쇄가 할 수 있는 것입니다.코드를 적게 쓰는 데 도움이 됩니다.이것의 또 다른 이점은 구조물을 참조하여 사용 방법을 알거나 구조물이 무엇을 하는지 이해할 필요가 없다는 것입니다.다른 답변은 이러한 이점이 없으며, 명확하지 않다면 가독성이 매우 높습니다.저는 읽기 쉬운 간단한 해결책을 위해 노력합니다.한 번은 iOS 앱을 작성했는데 훌륭하고 제가 얻을 수 있는 한 단순했습니다.그리고 나서 나는 같은 "앱"을 bash에 코드 5줄처럼 쓰고 욕을 했습니다.

임베디드 시스템도 있습니다.

언급URL : https://stackoverflow.com/questions/4393716/is-there-a-a-way-to-achieve-closures-in-c

반응형