문제

함수에 대한 포인터가 있다고 가정 해 봅시다 _stack_push(stack* stk, void* el). 나는 전화 할 수 있기를 원한다 curry(_stack_push, my_stack) 그리고 단지 취하는 함수를 되 찾으십시오 void* el. C는 런타임 기능 정의를 허용하지 않기 때문에 그렇게 할 방법을 생각할 수 없었지만 여기에 나보다 훨씬 영리한 사람들이 있다는 것을 알고 있습니다 :) 어떤 아이디어?

도움이 되었습니까?

해결책

C/C ++/Objective-C의 카레에 대해 논의하는 Laurent Dami의 논문을 찾았습니다.

C/C ++/Objective-C에서 더 많은 기능 재사성 성 기능

C에서 어떻게 구현되는지에 관심이 있습니다.

우리의 현재 구현은 기존 C 구성을 사용하여 카레링 메커니즘을 추가합니다. 이것은 컴파일러를 수정하는 것보다 훨씬 쉬웠으며 카레의 관심을 증명하기에 충분합니다. 그러나이 접근법에는 두 가지 단점이 있습니다. 첫째, 카레 기능은 유형 확인할 수 없으므로 오류를 피하기 위해 신중하게 사용해야합니다. 둘째, 카레 기능은 인수의 크기를 알 수 없으며 마치 정수의 크기 인 것처럼 계산합니다.

이 논문에는 구현이 포함되어 있지 않습니다 curry(), 그러나 당신은 그것이 어떻게 사용되는지 상상할 수 있습니다. 기능 포인터 그리고 변수 함수.

다른 팁

GCC는 중첩 함수의 정의를위한 확장을 제공합니다. 이것은 ISO 표준 C는 아니지만 질문에 매우 편리하게 답변 할 수 있기 때문에 약간의 관심이있을 수 있습니다. 간단히 말해서 중첩 된 기능은 부모 기능에 액세스 할 수 있으며 로컬 변수로 포인터가 부모 함수에 의해 반환 될 수 있습니다.

다음은 짧고 자명 한 예입니다.

#include <stdio.h>

typedef int (*two_var_func) (int, int);
typedef int (*one_var_func) (int);

int add_int (int a, int b) {
    return a+b;
}

one_var_func partial (two_var_func f, int a) {
    int g (int b) {
        return f (a, b);
    }
    return g;
}

int main (void) {
    int a = 1;
    int b = 2;
    printf ("%d\n", add_int (a, b));
    printf ("%d\n", partial (add_int, a) (b));
}

그러나이 구성에는 제한이 있습니다. 결과 함수에 대한 포인터를 유지하면

one_var_func u = partial (add_int, a);

함수 호출 u(0) 변수로 예기치 않은 동작이 발생할 수 있습니다. a 어느 u 읽기는 바로 그 후에 파괴되었습니다 partial 종료.

보다 GCC 문서 의이 섹션.

여기 내 머리 위의 첫 추측이 있습니다 (최상의 해결책이 아닐 수도 있음).

카레 함수는 힙에서 약간의 메모리를 할당하고 매개 변수 값을 해당 힙에 해당 메모리에 넣을 수 있습니다. 그런 다음 반환 된 기능이 해당 힙합 메모리에서 매개 변수를 읽어야한다는 점을 알 수 있습니다. 반환 된 함수의 인스턴스가 하나만 있으면 해당 매개 변수에 대한 포인터는 싱글 톤/글로벌에 저장 될 수 있습니다. 그렇지 않으면 반환 된 함수의 인스턴스가 둘 이상인 경우 카레는 반환 된 기능의 각 인스턴스를 만들어야한다고 생각합니다. 힙 할당 메모리에서 ( "매개 변수에 대한 포인터 가져 오기", "매개 변수 푸시"및 "다른 함수를 호출"하는 것과 같은 opcode를 작성함으로써 힙합 메모리에 "다른 함수를 호출하십시오). 이 경우 할당 된 메모리가 실행 가능한지 여부를 조심해야하며, 아마도 안티 바이러스 프로그램을 두려워해야 할 수도 있습니다.

다음은 C에서 카레를 수행하는 방법입니다.이 샘플 애플리케이션은 편의를 위해 C ++ ioStream 출력을 사용하는 동안 모든 C 스타일 코딩입니다.

이 접근법의 핵심은 a struct 배열이 포함되어 있습니다 unsigned char 이 배열은 함수에 대한 인수 목록을 작성하는 데 사용됩니다. 호출되는 함수는 배열로 푸시되는 인수 중 하나로 지정됩니다. 결과 배열은 실제로 기능과 인수의 폐쇄를 실행하는 프록시 함수에 제공됩니다.

이 예에서는 인수를 폐쇄로 푸시하기 위해 몇 가지 유형의 특정 헬퍼 기능을 제공합니다. pushMem() a struct 또는 다른 메모리 영역.

이 접근법은 메모리 영역의 할당이 필요하며, 그런 다음 폐쇄 데이터에 사용됩니다. 메모리 관리가 문제가되지 않도록이 메모리 영역에 스택을 사용하는 것이 가장 좋습니다. 또한 폐쇄 저장 메모리 영역을 만드는 것이 얼마나 큰지에 대한 문제가 있으므로 필요한 인수를위한 충분한 공간이 있지만 메모리 나 스택의 과도한 공간이 사용되지 않은 공간에 의해 채워지는 데 충분하지는 않습니다.

나는 폐쇄 데이터를 저장하는 데 사용되는 배열의 현재 사용되는 크기에 대한 추가 필드를 포함하는 약간 다르게 정의 된 폐쇄 구조를 사용하는 것을 실험했습니다. 그런 다음이 다른 클로저 구조는 수정 된 헬퍼 기능과 함께 사용하여 헬퍼 기능 사용자가 자신의 유지를 유지할 필요가 있습니다. unsigned char * 폐쇄 구조물에 인수를 추가 할 때 포인터.

메모와 경고

다음 예제 프로그램은 Visual Studio 2013으로 편집하고 테스트되었습니다.이 샘플의 출력은 아래에 제공됩니다. 이 예제와 함께 GCC 또는 Clang의 사용에 대해서는 확신이 없으며 테스트가 32 비트 응용 프로그램으로 인상을 받고 있기 때문에 64 비트 컴파일러로 볼 수있는 문제에 대해서는 확신하지 못합니다. 또한이 접근 방식은 Callee가 반환 된 후 호출 함수가 스택에서 인수를 팝업하는 표준 C 선언을 사용하는 함수와 함께 작동하는 것 같습니다.__cdecl 그리고 아닙니다 __stdcall Windows API).

실행 시간에 인수 목록을 작성한 다음 프록시 함수를 호출하기 때문에이 접근 방식을 사용하면 컴파일러가 인수 확인을 수행 할 수 없습니다. 이로 인해 컴파일러가 플래그 할 수없는 불일치 매개 변수 유형으로 인해 신비한 실패가 발생할 수 있습니다.

예제 응용 프로그램

// currytest.cpp : Defines the entry point for the console application.
//
// while this is C++ usng the standard C++ I/O it is written in
// a C style so as to demonstrate use of currying with C.
//
// this example shows implementing a closure with C function pointers
// along with arguments of various kinds. the closure is then used
// to provide a saved state which is used with other functions.

#include "stdafx.h"
#include <iostream>

// notation is used in the following defines
//   - tname is used to represent type name for a type
//   - cname is used to represent the closure type name that was defined
//   - fname is used to represent the function name

#define CLOSURE_MEM(tname,size) \
    typedef struct { \
        union { \
            void *p; \
            unsigned char args[size + sizeof(void *)]; \
        }; \
    } tname;

#define CLOSURE_ARGS(x,cname) *(cname *)(((x).args) + sizeof(void *))
#define CLOSURE_FTYPE(tname,m) ((tname((*)(...)))(m).p)

// define a call function that calls specified function, fname,
// that returns a value of type tname using the specified closure
// type of cname.
#define CLOSURE_FUNC(fname, tname, cname) \
    tname fname (cname m) \
    { \
        return ((tname((*)(...)))m.p)(CLOSURE_ARGS(m,cname)); \
    }

// helper functions that are used to build the closure.
unsigned char * pushPtr(unsigned char *pDest, void *ptr) {
    *(void * *)pDest = ptr;
    return pDest + sizeof(void *);
}

unsigned char * pushInt(unsigned char *pDest, int i) {
    *(int *)pDest = i;
    return pDest + sizeof(int);
}

unsigned char * pushFloat(unsigned char *pDest, float f) {
    *(float *)pDest = f;
    return pDest + sizeof(float);
}

unsigned char * pushMem(unsigned char *pDest, void *p, size_t nBytes) {
    memcpy(pDest, p, nBytes);
    return pDest + nBytes;
}


// test functions that show they are called and have arguments.
int func1(int i, int j) {
    std::cout << " func1 " << i << " " << j;
    return i + 2;
}

int func2(int i) {
    std::cout << " func2 " << i;
    return i + 3;
}

float func3(float f) {
    std::cout << " func3 " << f;
    return f + 2.0;
}

float func4(float f) {
    std::cout << " func4 " << f;
    return f + 3.0;
}

typedef struct {
    int i;
    char *xc;
} XStruct;

int func21(XStruct m) {
    std::cout << " fun21 " << m.i << " " << m.xc << ";";
    return m.i + 10;
}

int func22(XStruct *m) {
    std::cout << " fun22 " << m->i << " " << m->xc << ";";
    return m->i + 10;
}

void func33(int i, int j) {
    std::cout << " func33 " << i << " " << j;
}

// define my closure memory type along with the function(s) using it.

CLOSURE_MEM(XClosure2, 256)           // closure memory
CLOSURE_FUNC(doit, int, XClosure2)    // closure execution for return int
CLOSURE_FUNC(doitf, float, XClosure2) // closure execution for return float
CLOSURE_FUNC(doitv, void, XClosure2)  // closure execution for void

// a function that accepts a closure, adds additional arguments and
// then calls the function that is saved as part of the closure.
int doitargs(XClosure2 *m, unsigned char *x, int a1, int a2) {
    x = pushInt(x, a1);
    x = pushInt(x, a2);
    return CLOSURE_FTYPE(int, *m)(CLOSURE_ARGS(*m, XClosure2));
}

int _tmain(int argc, _TCHAR* argv[])
{
    int k = func2(func1(3, 23));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    XClosure2 myClosure;
    unsigned char *x;

    x = myClosure.args;
    x = pushPtr(x, func1);
    x = pushInt(x, 4);
    x = pushInt(x, 20);
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func1);
    x = pushInt(x, 4);
    pushInt(x, 24);               // call with second arg 24
    k = func2(doit(myClosure));   // first call with closure
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;
    pushInt(x, 14);              // call with second arg now 14 not 24
    k = func2(doit(myClosure));  // second call with closure, different value
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    k = func2(doitargs(&myClosure, x, 16, 0));  // second call with closure, different value
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    // further explorations of other argument types

    XStruct xs;

    xs.i = 8;
    xs.xc = "take 1";
    x = myClosure.args;
    x = pushPtr(x, func21);
    x = pushMem(x, &xs, sizeof(xs));
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    xs.i = 11;
    xs.xc = "take 2";
    x = myClosure.args;
    x = pushPtr(x, func22);
    x = pushPtr(x, &xs);
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func3);
    x = pushFloat(x, 4.0);

    float dof = func4(doitf(myClosure));
    std::cout << " main (" << __LINE__ << ") " << dof << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func33);
    x = pushInt(x, 6);
    x = pushInt(x, 26);
    doitv(myClosure);
    std::cout << " main (" << __LINE__ << ") " << std::endl;

    return 0;
}

테스트 출력

이 샘플 프로그램의 출력. 괄호 안의 숫자는 함수 호출이 이루어지는 기본의 줄 번호입니다.

 func1 3 23 func2 5 main (118) 8
 func1 4 20 func2 6 main (128) 9
 func1 4 24 func2 6 main (135) 9
 func1 4 14 func2 6 main (138) 9
 func1 4 16 func2 6 main (141) 9
 fun21 8 take 1; func2 18 main (153) 21
 fun22 11 take 2; func2 21 main (161) 24
 func3 4 func4 6 main (168) 9
 func33 6 26 main (175)
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top