valores - tipos de funciones en sql server
¿Cómo puedo hacer una función que devuelva una función? (7)
Imagen grande: tengo un módulo con funciones y un módulo con procedimientos y funciones sobre esas funciones.
Cuando combino dos funciones (desde la interfaz del módulo de la función):
double f1(double alpha, double x);
double f2(double beta, double x);
De varias maneras, (uno de ellos está agregando):
double OP_Addition(double (*f)(double,double) , double (*g)(double,double), double param1, double param2, double x);
No da ningún problema con la siguiente (parte de) la implementación:
z1 = (*f)(param1, x);
z2 = (*g)(param2, x);
y = z1 + z2;
return y;
Pero cuando quiero devolver un puntero a una función "nueva", algo como:
void *OP_PAdd( double (*f)(double,double), double param3 );
No puedo hacer que funcione correctamente ni hacer la "llamada" correcta. Quiero utilizar la "función" de salida como entrada en otras funciones.
¿Te refieres a algo como esto? La función decider()
devuelve un puntero a otra función, que luego se llama.
#include <stdio.h>
#include <stdlib.h>
typedef double(*fun)(double, double);
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double mul(double a, double b) {
return a * b;
}
fun decider(char op) {
switch(op) {
case ''+'': return add;
case ''-'': return sub;
case ''*'': return mul;
}
exit(1);
}
int main(void)
{
fun foo;
foo = decider(''+'');
printf("%f/n", foo(42.0, 24.0));
foo = decider(''-'');
printf("%f/n", foo(42.0, 24.0));
foo = decider(''*'');
printf("%f/n", foo(42.0, 24.0));
return 0;
}
Salida del programa:
66.000000
18.000000
1008.000000
EDITAR: Después de los comentarios en la respuesta @dbush , esta versión retrocede desde typedef
como un puntero, a solo una función. Da el mismo resultado, pero en decider()
se compila limpiamente y da el resultado correcto, sin importar si escribo return add;
o return &add;
#include <stdio.h>
#include <stdlib.h>
typedef double(fun)(double, double);
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double mul(double a, double b) {
return a * b;
}
fun *decider(char op) {
switch(op) {
case ''+'': return add; // return &add;
case ''-'': return sub;
case ''*'': return mul;
}
exit(1);
}
int main(void)
{
fun *foo;
foo = decider(''+'');
printf("%f/n", foo(42.0, 24.0));
foo = decider(''-'');
printf("%f/n", foo(42.0, 24.0));
foo = decider(''*'');
printf("%f/n", foo(42.0, 24.0));
return 0;
}
Al devolver una función desde otra función, la forma más limpia de hacerlo es con un typedef
:
typedef double (*ftype)(double, double);
Entonces puedes declarar tu función así:
ftype OP_PAdd( ftype f, double param3 )
{
....
return f1;
}
Puedes hacer esto sin un typedef
, pero está desordenado:
double (*OP_PAdd( double (*f)(double,double), double param3 ))(double,double)
{
return f1;
}
Por lo tanto, cuando tenga punteros de función como parámetros o valores de retorno de otras funciones, use un typedef
.
EDITAR:
Si bien puedes declarar el tipo así:
typedef double ftype(double, double);
Nunca se puede usar directamente un tipo como este en la práctica. Una función no puede devolver una función (solo un puntero a una función) y no se puede asignar una variable de este tipo.
Además, no es necesario desreferencia explícitamente un puntero de función para llamar a la función, por lo que el hecho de que el puntero esté oculto no es un gran problema. También es una convención definir punteros de función como typedef
. De la página del hombre para la signal
:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Dado que algunas personas son aparentemente paranoicas acerca de cómo escribir un truco para resolver este problema, esta es una forma menos hacky de hacerlo: use una estructura estática con setjmp y longjmp.
jmp_buf jb;
void *myfunc(void) {
static struct {
// put all of your local variables here.
void *new_data, *data;
int i;
} *_;
_ = malloc(sizeof(*_));
_.data = _;
if (!(_.new_data = (void *)(intptr_t)setjmp(jb)))
return _.data;
_.data = _.new_data;
/* put your code here */
free(_);
return NULL;
}
Para explicar lo que está sucediendo aquí, setjmp devolverá un valor de 0 cuando se crea el búfer de salto, de lo contrario devolverá el valor pasado por longjmp (por ejemplo, longjmp (jb, 5) hará que setjmp devuelva 5).
Entonces, lo que estamos haciendo es hacer que nuestra función devuelva un puntero a su estructura de datos asignada; y luego llamar a nuestro cierre como:
void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);
Tenga en cuenta que no se garantiza que un int sea lo suficientemente grande como para almacenar un puntero en todas las plataformas; por lo que puede necesitar crear un grupo de datos y devolver / pasar los datos por control (índice en el grupo).
Como dije antes, un cierre es solo una función con todos sus datos asignados en el montón.
He estado escribiendo hacks para juegos N64 y PSP durante años. La gente que afirma que esto es imposible probablemente nunca haya jugado con ese tipo de cosas. En su mayoría solo se reduce a la falta de experiencia.
Entonces quiere que una función devuelva un puntero a una función.
double retfunc()
{
return 0.5;
}
double (*fucnt)()
{
return retfunc;
}
main()
{
printf("%f/n", (*funct())());
}
Me voy a poner realmente hacky aquí, así que agárrate de tus calzones.
La api C estándar viene con 2 funciones llamadas setjmp
y longjmp
. Dejando a un lado los nombres, lo que básicamente hacen es almacenar una copia del estado actual (incluida la posición de la pila y los valores de registro) en un jmp_buf
(o, el nombre técnico, una continuation
).
Ahora, digamos que creas una función:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
Cuando llame a sfunc, se creará un marco de pila. Como no hay argumentos para esta función, la primera entrada en la pila será la dirección de retorno e inmediatamente después será el objeto sp_minus1.
¿Por qué es esto relevante? Bueno, la dirección de sp_minus1 es relativa al inicio del marco de pila. Si puede encontrar la dirección del marco de pila en jb
, puede cambiarlo ... por ejemplo, a una ubicación en el montón?
Lo que tenemos en este punto es una forma de crear marcos de pila para llamadas de función longjmp en el montón que pueden contener un estado adicional sobre el contexto en el que se llamaron; o en otras palabras, cierres.
No creo que haya visto a nadie usar longjmp / setjmp de esta manera, pero si estás buscando una forma de generar y devolver dinámicamente funciones en C, creo que esta sería tu mejor ruta.
EDITAR:
Aquí hay una implementación de ejemplo del truco que estoy describiendo:
#include <inttypes.h> // intptr_t
#include <setjmp.h> // longjmp, setjmp
#include <stdio.h> // printf
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
typedef struct {
jmp_buf jb;
int fixupc;
int fixupv[10];
size_t stack_size; // this is only an approximation
void *stack_ptr;
} CLOSURE;
int getclosure(CLOSURE *closure) {
unsigned int i, size;
void *i_ptr = &i, *sp;
unsigned char *data = (unsigned char *)(void *)closure->jb;
memset(closure, 0, sizeof(CLOSURE));
if (!setjmp(closure->jb)) {
printf("looking for 0x%08X.../n/n", (unsigned int)(intptr_t)i_ptr);
for (i = 0; i < sizeof(closure->jb); i++) {
memcpy(&sp, &data[i], sizeof(void *));
size = (unsigned int)(intptr_t)(sp - i_ptr);
if (size < 0x300) {
closure->fixupv[closure->fixupc++] = i;
printf(" fixup @ 0x%08X/n", (unsigned int)(intptr_t)sp);
if (sp > closure->stack_ptr) {
closure->stack_size = size;
closure->stack_ptr = sp;
}
}
}
if (!closure->stack_ptr)
return 0;
printf("/nsp @ 0x%08X/n", (unsigned int)(intptr_t)closure->stack_ptr);
printf("# of fixups = %i/n", closure->fixupc);
/*
* once we allocate the new stack on the heap, we''ll need to fixup
* any additional stack references and memcpy the current stack.
*
* for the sake of this example, I''m only fixing up the pointer
* to the stack itself.
*
* after that, we would have successfully created a closure...
*/
closure->stack_size = 1024;
sp = malloc(closure->stack_size);
memcpy(sp, closure->stack_ptr, closure->stack_size);
memcpy(&data[closure->fixupv[0]], &sp, sizeof(void *));
closure->stack_ptr = sp;
return 1;
} else {
/*
* to this bit of code right here
*/
printf("holy shit!/n");
return 0;
};
}
void newfunc(CLOSURE *closure) {
longjmp(closure->jb, 1);
}
void superfunc(CLOSURE *closure) {
newfunc(closure);
}
int main(int argc, char *argv[]) {
CLOSURE c;
if (getclosure(&c)) {
printf("/nsuccess!/n");
superfunc(&c);
free(c.stack_ptr);
return 0;
}
return 0;
}
Esto es, técnicamente, una forma de aplastamiento de pila, por lo que, de forma predeterminada, GCC generará canarios de pila que harán abortar el programa. Si compila con ''-fno-stack-protection'', funcionará.
Otras respuestas son correctas e interesantes, pero debe tener en cuenta que en C99 portátil, no hay forma de tener closures genuinos como funciones C (y esta es una limitación fundamental de C). Si no está al tanto de los cierres, lea cuidadosamente la página wiki (y también lea SICP , especialmente su §1.3 ). Sin embargo, tenga en cuenta que en C++11 tiene cierres, usando std::function y lambda-expressions . Y la mayoría de los otros lenguajes de programación (Ocaml, Haskell, Javascript, Lisp, Clojure, Python, ....) tienen cierres.
Debido a la falta de cierres genuinos en C ("matemáticamente" los únicos valores cerrados en las funciones C son variables globales o estáticas o literales), la mayoría de las bibliotecas que aceptan punteros a función C proporcionan una API que maneja callbacks con algunos datos del cliente (un ejemplo simple podría ser qsort_r , pero más en serio mira dentro de GTK ). Los datos del cliente (generalmente un puntero opaco) se pueden usar para mantener los valores cerrados. Probablemente desee seguir una convención similar (por lo tanto, pase sistemáticamente el puntero de función como devoluciones de llamada con algunos datos de cliente adicionales), por lo que deberá cambiar las firmas de sus funciones C (en lugar de pasar solo un puntero de función sin procesar, pasará un puntero a la función y algunos datos del cliente como devolución de llamada, para "emular" cierres).
A veces puede generar una función C en tiempo de ejecución (utilizando funciones no estándar, probablemente con la ayuda del sistema operativo o alguna biblioteca externa). Puede usar alguna biblioteca de compilación JIT como GNU Lightning , libjit (ambos generarán rápidamente algún código de ejecución lenta), asmjit (generará cada instrucción de máquina explícitamente, y es su responsabilidad emitir un código rápido x86-64), GCCJIT o LLVM (ambos están por encima de los compiladores existentes, por lo que se pueden usar para emitir, un poco lentamente, algún código optimizado). En los sistemas POSIX y Linux, también podría emitir algún código C en algún archivo temporal /tmp/tempcode.c
, fork una compilación (por ejemplo, gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so
) de ese código en un complemento, y cargar dinámicamente ese complemento generado usando dlopen(3) y dlsym(3) .
Por cierto, no sabemos cuál es la aplicación real que está codificando, pero podría considerar incorporar algún intérprete, por ejemplo, Lua o Guile . A continuación, utilizará y proporcionará devoluciones de llamada al evaluador / intérprete incorporado.
en C, puede devolver el puntero a la función, pero para hacer eso, la función necesita existir primero, y la creación dinámica de funciones no es algo que C dice que es posible, no importa cómo hacerlo
si su código va a funcionar solo en un sistema operativo y un procesador (y probablemente en otras restricciones), es posible que:
- asignar página de memoria
- escriba los datos y el código de la máquina haciendo lo que quiera, llamando funciones pasadas por un puntero, etc.
- cambiar la protección de memoria de lectura / escritura para leer / ejecutar
- puntero de retorno a la función creada
- no se preocupe, necesita 4kB por función
probablemente haya bibliotecas en alguna parte para eso, pero necesariamente no son portátiles