Pergunta

Dizem que eu tenho um ponteiro para uma função _stack_push(stack* stk, void* el).Eu quero ser capaz de chamar curry(_stack_push, my_stack) e voltar uma função que leva void* el.Eu não conseguia pensar em uma maneira de fazê-lo, desde que C não permitir que o tempo de execução da definição de função, mas eu sei que há muito mais esperta pessoas do que de mim aqui :).Qualquer idéias?

Foi útil?

Solução

Encontrei um artigo de Laurent Dami que discute a curry em C/C ++/Objective-C:

Reutilização mais funcional em C/C ++/Objective-C com funções ao curry

De interesse de como é implementado em C:

Nossa implementação atual usa construções C existentes para adicionar o mecanismo de curry. Isso foi muito mais fácil de fazer do que modificar o compilador e é suficiente para provar o interesse de curryar. Essa abordagem tem duas desvantagens, no entanto. Primeiro, as funções ao curry não podem ser verificadas pelo tipo e, portanto, exigem uso cuidadoso para evitar erros. Segundo, a função de curry não pode saber o tamanho de seus argumentos e os conta como se fossem todo o tamanho de um número inteiro.

O artigo não contém uma implementação de curry(), mas você pode imaginar como é implementado usando e funções variádicas.

Outras dicas

O GCC fornece uma extensão para a definição de funções aninhadas.Embora este não seja o padrão ISO C, isto pode ser de algum interesse, pois permite responder à pergunta muito convenientemente.Em suma, a função aninhada pode aceder a principal função de variáveis locais e ponteiros para eles pode ser retornado pela função de pai também.

Aqui está um pequeno, auto-explicativo exemplo:

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

Há no entanto uma limitação para esta construção.Se você manter um ponteiro para a função, como no

one_var_func u = partial (add_int, a);

a chamada de função u(0) pode resultar em um comportamento inesperado, como a variável a que u lê foi destruído logo após partial terminada.

Ver esta seção da documentação do GCC.

Aqui está o meu primeiro palpite no topo da minha cabeça (pode não ser a melhor solução).

A função Curry pode alocar alguma memória da pilha e colocar os valores dos parâmetros nessa memória alocada por heap. O truque é então para a função retornada saber que deveria ler seus parâmetros a partir dessa memória alocada por heap. Se houver apenas uma instância da função retornada, um ponteiro para esses parâmetros poderá ser armazenado em um singleton/global. Caso contrário, se houver mais de uma instância da função retornada, acho que o curry precisa criar cada instância da função retornada na memória alocada por heap (Ao escrever OpCodes como "Obtenha esse ponteiro para os parâmetros", "Empurre os parâmetros" e "Invoque essa outra função" na memória alocada por heap). Nesse caso, você precisa tomar cuidado se a memória alocada é executável e talvez (eu não sei) até ter medo de programas antivírus.

Aqui está uma abordagem para fazer o curry em C. Enquanto esse aplicativo de amostra está usando a saída C ++ iostream por conveniência, é tudo codificação de estilo C.

A chave para essa abordagem é ter um struct que contém uma variedade de unsigned char E essa matriz é usada para criar uma lista de argumentos para uma função. A função a ser chamada é especificada como um dos argumentos que são empurrados para a matriz. A matriz resultante é dada a uma função de proxy que realmente executa o fechamento da função e dos argumentos.

Neste exemplo, eu forneço algumas funções auxiliares específicas do tipo para empurrar argumentos para o fechamento, bem como um genérico pushMem() função para empurrar um struct ou outra região de memória.

Essa abordagem requer alocação de uma área de memória que é usada para os dados de fechamento. Seria melhor usar a pilha para esta área de memória para que o gerenciamento de memória não se torne um problema. Há também o problema do quão grande para tornar a área de memória de armazenamento de fechamento, para que haja espaço suficiente para os argumentos necessários, mas não tão grande que o excesso de espaço na memória ou na pilha é absorvido pelo espaço não utilizado.

Eu experimentei o uso de uma estrutura de fechamento definida ligeiramente diferente, que contém um campo adicional para o tamanho atualmente usado da matriz usada para armazenar os dados de fechamento. Esta estrutura de fechamento diferente é então usada com funções ajudantes modificadas que remove a necessidade do usuário das funções do auxiliar para manter seus próprios unsigned char * ponteiro ao adicionar argumentos à estrutura de fechamento.

Notas e advertências

O programa de exemplo a seguir foi compilado e testado com o Visual Studio 2013. A saída desta amostra é fornecida abaixo. Não tenho certeza sobre o uso do GCC ou CLANG com este exemplo, nem tenho certeza de que os problemas que podem ser vistos com um compilador de 64 bits, pois tenho a impressão de que meus testes estavam com um aplicativo de 32 bits. Além disso, isso se aproximaria só pareceria funcionar com funções que usam a declaração C padrão em que a função de chamada lida com os argumentos da pilha após o retorno do callee (__cdecl e não __stdcall na API do Windows).

Como estamos construindo a lista de argumentos no tempo de execução e, em seguida, chamando uma função de proxy, essa abordagem não permite que o compilador execute uma verificação nos argumentos. Isso pode levar a falhas misteriosas devido a tipos de parâmetros incompatíveis que o compilador não consegue sinalizar.

Exemplo de aplicativo

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

Saída de teste

Saída deste programa de amostra. O número entre parênteses é o número da linha no principal onde a chamada de função é feita.

 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)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top