wake-up-neo.com

Wie kann ich eine Funktion erstellen, die eine Funktion zurückgibt?

Gesamtbild: Ich habe ein Modul mit Funktionen und ein Modul mit Prozeduren und Funktionen über diese Funktionen.

Wenn ich zwei Funktionen kombiniere (über die Modulschnittstelle der Funktion):

double f1(double alpha, double x);
double f2(double beta, double x);

In mehrfacher Hinsicht (eine davon fügt hinzu):

double OP_Addition(double (*f)(double,double) , double (*g)(double,double), double param1, double param2, double x);

Gibt kein Problem mit der folgenden (Stück) Implementierung:

z1 = (*f)(param1, x);
z2 = (*g)(param2, x);
y = z1 + z2;
return y;

Aber wenn ich einen Zeiger auf eine "neue" Funktion zurückgeben möchte, so etwas wie:

void *OP_PAdd( double (*f)(double,double), double param3 );

Ich kann es nicht richtig zum Laufen bringen, noch den richtigen "Anruf" machen. Ich möchte den Ausgang "function" als Eingang für andere Funktionen verwenden.

40
MCL

Andere Antworten sind richtig und interessant, aber Sie sollten sich bewusst sein, dass es in portablem C99 keine Möglichkeit gibt, echte Abschlüsse als C-Funktionen (und dies) zu haben ist eine Grundlegende Einschränkung von C). Wenn Sie nicht wissen, welche Closures vorhanden sind, lesen Sie die Wiki-Seite sorgfältig durch (und lesen Sie auch SICP , insbesondere seine - §1. ). Beachten Sie jedoch, dass Sie in C++ 11 Abschlüsse haben, indem Sie std :: function und lambda-expressions verwenden. Und die meisten anderen Programmiersprachen (Ocaml, Haskell, Javascript, LISP, Clojure, Python, ....) haben Abschlüsse.

Aufgrund des Fehlens von echten Closures in C ("mathematisch" sind die einzigen geschlossenen Werte in C-Funktionen globale oder statische Variablen oder Literale) bieten die meisten Bibliotheken, die C-Funktionszeiger akzeptieren, eine API-Behandlung Rückrufe mit einigen Clients data (ein einfaches Beispiel könnte qsort_r sein, aber genauer gesagt GTK ). Diese Client-Daten (im Allgemeinen ein undurchsichtiger Zeiger) können verwendet werden, um geschlossene Werte beizubehalten. Sie möchten wahrscheinlich einer ähnlichen Konvention folgen (also systematisch Funktionszeiger als Rückrufe mit einigen zusätzlichen Clientdaten übergeben), also müssen Sie die Signaturen Ihrer C-Funktionen ändern (anstatt nur einen unformatierten Funktionszeiger zu übergeben, übergeben Sie sowohl ein Funktionszeiger als auch einige Client-Daten als Rückruf (um Abschlüsse zu "emulieren").

Sie könnten manchmal zur Laufzeit eine C-Funktion generieren (unter Verwendung nicht standardmäßiger Funktionen, möglicherweise mithilfe des Betriebssystems oder einer externen Bibliothek). Sie könnten eine JIT-Kompilierung Bibliothek verwenden, wie GNU-Blitz , libjit (beide würden schnell langsam laufenden Code erzeugen), asmjit (Sie generieren jede Maschinenanweisung explizit, und es liegt in Ihrer Verantwortung, schnellen x86-64-Code auszugeben), GCCJIT oder LLVM (beide sind über den vorhandenen Compilern, können also verwendet werden, um etwas langsam optimierten Code auszugeben). Auf POSIX- und Linux-Systemen können Sie auch C-Code in einer temporären Datei /tmp/tempcode.c, eine Zusammenstellung aufteilen (z. B. gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so) dieses Codes in ein Plugin und lade das erzeugte Plugin dynamisch mit dlopen (3) & dlsym (3) ..

Übrigens wissen wir nicht, was die eigentliche Anwendung ist, die Sie codieren, aber Sie könnten in Betracht ziehen, einen Interpreter, z. Lua oder Guile . Anschließend verwenden Sie den eingebetteten Evaluator/Interpreter und stellen ihm Rückrufe zur Verfügung.

25

Wenn Sie eine Funktion aus einer anderen Funktion zurückgeben, ist dies am saubersten mit einer typedef:

typedef double (*ftype)(double, double);

Dann können Sie Ihre Funktion folgendermaßen deklarieren:

ftype OP_PAdd( ftype f, double param3 )
{
    ....
    return f1;
}

Sie können dies ohne eine typedef tun, aber es ist chaotisch:

double (*OP_PAdd( double (*f)(double,double), double param3 ))(double,double)
{
    return f1;
}

Wenn Sie also Funktionszeiger als Parameter oder Rückgabewerte anderer Funktionen haben, verwenden Sie eine typedef.

BEARBEITEN:

Während Sie den Typ so deklarieren könnten:

typedef double ftype(double, double);

Sie können einen solchen Typ in der Praxis niemals direkt verwenden. Eine Funktion kann keine Funktion zurückgeben (nur einen Zeiger auf eine Funktion), und eine Variable dieses Typs kann nicht zugewiesen werden. 

Außerdem müssen Sie einen Funktionszeiger nicht explizit dereferenzieren, um die Funktion aufzurufen. Daher ist die Tatsache, dass der Zeiger selbst verborgen ist, kein großes Problem. Es ist auch üblich, Funktionszeiger als typedef zu definieren. Auf der man-Seite für signal :

   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);
32
dbush

Meinst du so etwas? Die Funktion decider() gibt einen Zeiger auf eine andere Funktion zurück, die dann aufgerufen wird.

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

Programmausgabe:

66.000000
18.000000
1008.000000

EDIT: Nach den Kommentaren unter @dbush answer geht diese Version von der typedef als Zeiger auf eine Funktion zurück. Es gibt die gleiche Ausgabe, aber in decider() kompiliert es sauber und gibt die korrekte Ausgabe aus, unabhängig davon, ob ich return add; oder return &add; schreibe

#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;
}
13
Weather Vane

in C können Sie den Zeiger auf die Funktion zurückgeben. Um dies zu tun, muss die Funktion jedoch zuerst vorhanden sein. Das dynamische Erstellen von Funktionen ist nichts, was C sagt, ist möglich, egal wie es geht

wenn Ihr Code nur auf einem Betriebssystem und einem Prozessor (und möglicherweise einigen anderen Einschränkungen) ausgeführt wird, können Sie Folgendes tun:

  1. speicherplatz zuweisen
  2. schreibe Daten und Maschinencode, mach was du willst, Funktionen aufrufen, die durch Zeiger übergeben werden usw.
  3. speicherschutz von Lesen/Schreiben in Lesen/Ausführen ändern
  4. zeiger auf erstellte Funktion zurückgeben
  5. machen Sie sich keine Sorgen, dass Sie 4kB pro Funktion benötigen

es gibt wahrscheinlich irgendwo Bibliotheken dafür, aber nicht unbedingt tragbar

3
Jordan Szubert

Sie möchten also, dass eine Funktion einen Zeiger auf eine Funktion zurückgibt.

double retfunc()
{
   return 0.5;
}

double (*fucnt)()
{
  return retfunc;
}

main()
{
   printf("%f\n", (*funct())());
}
0
Bing Bang

Da einige Leute scheinbar paranoid sind, einen Hack zu schreiben, um dieses Problem zu lösen, können Sie dies auf weniger gehackte Weise tun: Verwenden Sie eine statische Struktur mit setjmp und 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;
}

Um zu erklären, was hier vor sich geht, gibt setjmp beim Erstellen des Sprungpuffers den Wert 0 zurück, andernfalls wird der von longjmp übergebene Wert zurückgegeben (z. B. longjmp (jb, 5) bewirkt, dass setjmp 5 zurückgibt).

Was wir also tun, ist, dass unsere Funktion einen Zeiger auf die zugewiesene Datenstruktur zurückgibt. und dann unsere Schließung nennen:

void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);

Bitte beachten Sie, dass ein int nicht garantiert groß genug ist, um einen Zeiger auf allen Plattformen zu speichern. Daher müssen Sie möglicherweise einen Datenpool erstellen und die Daten per Handle (Index im Pool) zurückgeben/übergeben.

Wie ich bereits gesagt habe, ist eine Schließung nur eine Funktion, bei der alle Daten auf dem Heap zugewiesen werden.

Ich schreibe seit Jahren Hacks für N64- und PSP-Spiele. Die Leute, die behaupten, dass dies unmöglich ist, haben wahrscheinlich nie mit solchen Sachen gebastelt. Meist läuft es nur auf mangelnde Erfahrung.

0
DeftlyHacked