Pregunta

Digamos que tengo un puntero a una función _stack_push (stack * stk, void * el) . Quiero poder llamar a curry (_stack_push, my_stack) y recuperar una función que solo toma void * el . No se me ocurre una manera de hacerlo, ya que C no permite la definición de la función de tiempo de ejecución, pero sé que aquí hay personas mucho más inteligentes que yo :). ¿Alguna idea?

¿Fue útil?

Solución

Encontré un artículo de Laurent Dami que habla sobre curry en C / C ++ / Objective-C:

Más reutilización funcional en C / C ++ / Objective-c con funciones curry

De interés sobre cómo se implementa en C:

  

Nuestra implementación actual utiliza construcciones C existentes para agregar el mecanismo de curry. Esto fue mucho más fácil de hacer que modificar el compilador, y es suficiente para demostrar el interés del curry. Sin embargo, este enfoque tiene dos inconvenientes. Primero, las funciones currificadas no pueden ser verificadas por tipo, y por lo tanto requieren un uso cuidadoso para evitar errores. En segundo lugar, la función de curry no puede conocer el tamaño de sus argumentos, y los cuenta como si fueran del tamaño de un entero.

El documento no contiene una implementación de curry () , pero puede imaginar cómo se implementa usando punteros de función y funciones variadas .

Otros consejos

GCC proporciona una extensión para la definición de funciones anidadas. Aunque este no es el estándar ISO C, puede ser de algún interés, ya que permite responder la pregunta de manera bastante conveniente. En resumen, la función anidada puede acceder a las variables locales de la función principal y los punteros también pueden ser devueltos por la función principal.

Aquí hay un breve ejemplo que se explica por sí mismo:

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

Sin embargo, hay una limitación para esta construcción. Si mantiene un puntero a la función resultante, como en

one_var_func u = partial (add_int, a);

la función llamada u (0) puede provocar un comportamiento inesperado, ya que la variable a que u lee fue destruida justo después de < code> parcial terminado.

Consulte esta sección de la documentación de GCC .

Aquí está mi primera suposición de la parte superior de mi cabeza (puede que no sea la mejor solución).

La función curry podría asignar algo de memoria del montón y colocar los valores de los parámetros en esa memoria asignada al montón. El truco es que la función devuelta sepa que se supone que debe leer sus parámetros de esa memoria asignada en el montón. Si solo hay una instancia de la función devuelta, se puede almacenar un puntero a esos parámetros en un singleton / global. De lo contrario, si hay más de una instancia de la función devuelta, entonces creo que el curry necesita crear cada instancia de la función devuelta en la memoria asignada en el montón (escribiendo códigos de operación como & obtener el puntero para los parámetros '', '' empujan los parámetros '', e '' invocan esa otra función '' en la memoria asignada en el montón). En ese caso, debe tener cuidado si la memoria asignada es ejecutable, y tal vez (no sé) incluso tener miedo de los programas antivirus.

Aquí hay un enfoque para hacer currículum en C. Si bien esta aplicación de muestra utiliza la salida iostream de C ++ por conveniencia, es toda la codificación de estilo C.

La clave de este enfoque es tener una struct que contiene una matriz de unsigned char y esta matriz se utiliza para crear una lista de argumentos para una función. La función a llamar se especifica como uno de los argumentos que se insertan en la matriz. La matriz resultante se entrega a una función proxy que realmente ejecuta el cierre de la función y los argumentos.

En este ejemplo, proporciono un par de funciones auxiliares específicas de tipo para insertar argumentos en el cierre, así como una función genérica pushMem () para insertar una struct u otra región de memoria.

Este enfoque requiere la asignación de un área de memoria que luego se utiliza para los datos de cierre. Sería mejor usar la pila para esta área de memoria para que la administración de memoria no se convierta en un problema. También existe el problema de qué tan grande es hacer el área de memoria de almacenamiento de cierre para que haya suficiente espacio para los argumentos necesarios, pero no tan grande como para que el espacio no utilizado ocupe el exceso de espacio en la memoria o en la pila.

He experimentado con el uso de una estructura de cierre ligeramente diferente que contiene un campo adicional para el tamaño de la matriz utilizada actualmente para almacenar los datos de cierre. Esta estructura de cierre diferente se usa luego con una función auxiliar modificada que elimina la necesidad de que el usuario de las funciones auxiliares mantenga su propio puntero unsigned char * al agregar argumentos a la estructura de cierre.

Notas y advertencias

El siguiente programa de ejemplo fue compilado y probado con Visual Studio 2013. El resultado de este ejemplo se proporciona a continuación. No estoy seguro sobre el uso de GCC o CLANG con este ejemplo ni estoy seguro de los problemas que se pueden ver con un compilador de 64 bits, ya que tengo la impresión de que mi prueba fue con una aplicación de 32 bits. Además, este enfoque solo parecería funcionar con funciones que usan la declaración C estándar en la que la función de llamada maneja los argumentos emergentes de la pila después de que la persona que llama regresa ( __cdecl y no __stdcall en la API de Windows).

Dado que estamos construyendo la lista de argumentos en tiempo de ejecución y luego llamando a una función proxy, este enfoque no permite que el compilador realice una verificación de los argumentos. Esto podría conducir a fallas misteriosas debido a tipos de parámetros no coincidentes que el compilador no puede marcar.

Ejemplo de aplicación

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

Prueba de salida

Salida de este programa de muestra. El número entre paréntesis es el número de línea en el principal donde se realiza la llamada a la función.

 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top