C’è un modo per ottenere chiusure in C

Vorrei che questo lavoro, ma non:

#include <stdio.h>

typedef struct closure_s {
  void (*incrementer) ();
  void (*emitter) ();
} closure;

closure emit(int in) {

  void incrementer() {
    in++;
  }

  void emitter() {
    printf("%d\n", in);
  }

  return (closure) {
    incrementer,
    emitter
  };
}

main() {
  closure test[] = {
    emit(10),
    emit(20)
  };

  test[0] . incrementer();
  test[1] . incrementer();

  test[0] . emitter();
  test[1] . emitter();
}

Effettivamente non compila e funziona per 1 esempio … ma il secondo non riesce. Qualsiasi idea di come ottenere chiusure in C?

Sarebbe veramente fantastico!!!

InformationsquelleAutor kristopolous | 2010-12-09



6 Replies
  1. 19

    Utilizzando FFCALL,

    #include <callback.h>
    #include <stdio.h>
    static void incrementer_(int *in) {
        ++*in;
    }
    static void emitter_(int *in) {
        printf("%d\n", *in);
    }
    int main() {
        int in1 = 10, in2 = 20;
        int (*incrementer1)() = alloc_callback(&incrememnter_, &in1);
        int (*emitter1)() = alloc_callback(&emitter_, &in1);
        int (*incrementer2)() = alloc_callback(&incrememnter_, &in2);
        int (*emitter2)() = alloc_callback(&emitter_, &in2);
        incrementer1();
        incrementer2();
        emitter1();
        emitter2();
        free_callback(incrementer1);
        free_callback(incrementer2);
        free_callback(emitter1);
        free_callback(emitter2);
    }

    Ma di solito in C si finiscono extra argomenti intorno a falsi chiusure.


    Apple ha una estensione non standard di C chiamato blocchi, che funzionano in modo molto simile chiusure.

    • Non si blocchi solo una parte di Objective-C? Si può compilare puro C come C (e non semplicemente ObjC senza oggetti o messaggistica) e di utilizzare i blocchi?
    • Credo che Apple ha aggiunto blocchi di supporto per GCC e clang in tutti i C-relative lingue, e non solo Objective-c, Ma non ho un OS X macchina per il test su.
    • hai ragione. Appena testato e funziona in pura C. non prova C++, ma che probabilmente funziona, troppo.
    • Io segno la risposta, probabilmente. Sto ancora pensando se io sono solo un piccolo dainty piccolo trucco lontano da raggiungere con quel bel sintassi, senza dover ricorrere a un sacco di stile c puntatori e de-riferimenti.
  2. 7

    GCC e clang sono i blocchi di estensione, che è essenzialmente chiusure in C.

    • I blocchi non sono in GCC mainline, solo Apple forcella.
    • Apple ha implementato blocchi di Apple copia del GCC fonti e il Fragore del compilatore LLVM front-end.
  3. 4

    ANSI C non ha un supporto per la chiusura, così come le funzioni nidificate. Soluzione per questo è di utilizzo semplice “struttura”.

    Semplice esempio di chiusura per la somma di due numeri.

    //Structure for keep pointer for function and first parameter
    typedef struct _closure{
        int x;
        char* (*call)(struct _closure *str, int y);
    } closure;
    
    
    //An function return a result call a closure as string
    char *
    sumY(closure *_closure, int y) {
        char *msg = calloc(20, sizeof(char));
        int sum = _closure->x + y;
        sprintf(msg, "%d + %d = %d", _closure->x, y, sum);
        return msg;
    }
    
    
    //An function return a closure for sum two numbers
    closure *
    sumX(int x) {
        closure *func = (closure*)malloc(sizeof(closure));
        func->x = x;
        func->call = sumY;
        return func;
    }

    Uso:

    int main (int argv, char **argc)
    {
    
        closure *sumBy10 = sumX(10);
        puts(sumBy10->call(sumBy10, 1));
        puts(sumBy10->call(sumBy10, 3));
        puts(sumBy10->call(sumBy10, 2));
        puts(sumBy10->call(sumBy10, 4));
        puts(sumBy10->call(sumBy10, 5));
    }

    Risultato:

    10 + 1 = 11
    10 + 3 = 13
    10 + 2 = 12
    10 + 4 = 14
    10 + 5 = 15

    Sul C++11, sarà ottenuta mediante l’uso di espressioni lambda.

    #include <iostream>
    int main (int argv, char **argc)
    {
        int x = 10;
        auto sumBy10 = [x] (int y) {
            std::cout << x << " + " << y << " = " << x + y << std::endl;
        };
        sumBy10(1);
        sumBy10(2);
        sumBy10(3);
        sumBy10(4);
        sumBy10(5);
    }

    Risultato, dopo la compilazione, con una bandiera -std=c++11.

    10 + 1 = 11
    10 + 2 = 12
    10 + 3 = 13
    10 + 4 = 14
    10 + 5 = 15
  4. 2

    GCC supporta le funzioni interne, ma non chiusure. C++0x avrà chiusure. Nessuna versione di C, che io sappia, e certamente non la versione standard, prevede che il livello di impressionante.

    Phoenix, che è parte di Boost fornisce chiusure in C++.

    • Si può pensare a un modo furbo per massaggi C nel raggiungimento di ciò che stiamo cercando qui senza inquinare la sintassi o che richiedono librerie extra? Sembra così vicino.
    • No. Poiché non c’è nessuna lingua a livello di supporto, è necessario il supporto della libreria. Dal momento che il più vicino si può arrivare a estendere il linguaggio stesso è per l’utilizzo di macro e non c’è nessun sovraccarico, avrete bisogno di una sintassi speciale, forse nascosto dietro le macro.
    • Beh, io so che non è inteso come funzione di C — ma facendo orientata agli oggetti ereditarietà attraverso una serie di struct e macro magia non era anche un uso previsto. Il problema è il riutilizzo di spazio di indirizzi e la temporalità dei puntatori…Il motivo è semplice, “C non è quel tipo di linguaggio”. E anche se si ottiene la funzione privata e parte variabile, che è quello che OO offre, ti mancano ancora funzioni anonime – una componente importante per il che lo rende utile; e che sarebbe indubbiamente richiedono macro.
    • C è in grado di supportare altri stili di programmazione, ma si devono ancora fare affidamento su C capacità e procedurali di sintassi. Anche l’esempio di OO stile in C si basa ancora sui puntatori e procedure. Le chiusure sono ancora al di là delle capacità di “C”; se si guarda FFCALL di origine, utilizza linguaggio assembly per costruire chiusure. FFCALL è quanto di più pulito, come si può ottenere.
  5. 2

    Una Definizione di Lavoro di una Chiusura con un Esempio JavaScript

    Chiusura è un tipo di oggetto che contiene un puntatore o un riferimento di qualche tipo di una funzione che deve essere eseguita con l’istanza dei dati necessari per la funzione.

    Un esempio in JavaScript da https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures è

    function makeAdder(x) {
      return function(y) { //create the adder function and return it along with
        return x + y;      //the captured data needed to generate its return value
      };
    }

    che potrebbe poi essere utilizzato come:

    var add5 = makeAdder(5);  //create an adder function which adds 5 to its argument
    
    console.log(add5(2));  //displays a value of 2 + 5 or 7

    Alcuni Ostacoli da Superare con C

    Il linguaggio di programmazione C è un linguaggio tipizzato staticamente, a differenza di JavaScript, né la raccolta dei rifiuti, e alcune altre caratteristiche che lo rendono facile per fare le chiusure in JavaScript o altri linguaggi intrinseca con il supporto per le chiusure.

    Un grande ostacolo per le chiusure in C Standard è la mancanza di supporto per la lingua per il tipo di costruzione in JavaScript esempio in cui la chiusura comprende non solo la funzione ma anche una copia dei dati acquisiti quando la chiusura viene creato, un modo di risparmio energetico che può quindi essere utilizzato quando la chiusura viene eseguita con ulteriori argomentazioni fornite al momento della chiusura viene richiamata la funzione.

    Tuttavia, C sono alcuni elementi di base che possono fornire gli strumenti per la creazione di una sorta di chiusura. Alcune difficoltà sono (1) la gestione della memoria è compito del programmatore, non la raccolta dei rifiuti, (2) le funzioni e i dati sono separati, nessuna classe o di una classe di tipo meccanica, (3) staticamente tipizzato in modo che nessun tempo di esecuzione di individuazione dei tipi di dati o di dati di dimensioni, e (4) lingua povera e servizi per l’acquisizione dei dati dello stato al momento della chiusura è creato.

    Una cosa che rende qualcosa di un impianto di chiusura è possibile con la C è la void * puntatore e l’utilizzo di unsigned char come una sorta di uso generale tipo di memoria che viene poi trasformata in altri tipi attraverso la fusione.

    Un’Implementazione Standard C e un Po ‘ di Stretching Qua e Là

    NOTA: Il seguente esempio dipende da una pila a base di argomento passaggio di convenzione, in quanto è utilizzato con la maggior parte x86 a 32 bit compilatori. La maggior parte dei compilatori permettono anche una convenzione di chiamata a essere specificato altre di stack in base argomento di passaggio, come il __fastcall modificatore di Visual Studio. L’impostazione predefinita per x64 e 64 bit di Visual Studio è quello di utilizzare il __fastcall convenzione per impostazione predefinita, in modo che gli argomenti della funzione sono passati nei registri e non in pila. Vedere Panoramica delle x64 Convenzioni di Chiamata in Microsoft MSDN nonché Come impostare gli argomenti di una funzione in assemblea durante l’esecuzione in un 64bit applicazione su Windows? nonché le varie risposte e commenti Come variabile di argomenti implementato in gcc? .

    Una cosa che possiamo fare è quello di risolvere questo problema, che forniscono un qualche tipo di chiusura impianto per la C è per semplificare il problema. Meglio forniscono l ‘ 80% di soluzione che è utile per la maggior parte delle applicazioni, che non è una soluzione a tutto.

    Una tale semplificazione è solo per supportare le funzioni che non restituiscono un valore, in altre parole le funzioni dichiarate come void func_name(). Abbiamo intenzione di mollare la verifica del tipo in fase di compilazione della funzione elenco di argomenti dal momento che questo approccio costruisce l’argomento della funzione elenco in fase di esecuzione. Nessuno di queste cose che ci stanno dando sono di poco conto, quindi la domanda è se il valore di questo approccio alle chiusure in C, supera di quello che ci stanno dando.

    Prima di tutto consente di definire la nostra chiusura area dati. La chiusura di dati rappresenta l’area di memoria che vogliamo usare per contenere le informazioni di cui abbiamo bisogno per una chiusura. La quantità minima di dati che posso pensare è un puntatore alla funzione per eseguire una copia dei dati che devono essere forniti alla funzione come argomenti.

    In questo caso abbiamo intenzione di fornire qualsiasi stato catturato dati necessari per la funzione come argomento della funzione.

    Vorremmo anche avere alcune informazioni di base, guardie di sicurezza in modo che non riusciremo ragionevolmente sicuro. Purtroppo il corrimano di sicurezza sono un po ‘ debole con alcune delle soluzioni che stiamo usando per attuare una forma di chiusure.

    Il Codice Sorgente

    Il seguente codice sorgente è stato sviluppato utilizzando Visual Studio 2017 Community Edition in un .c C file di origine.

    L’area dati è una struttura che contiene un po ‘ di gestione dei dati, un puntatore a funzione, e un indeterminato zona di dati.

    typedef struct {
        size_t  nBytes;    //current number of bytes of data
        size_t  nSize;     //maximum size of the data area
        void(*pf)();       //pointer to the function to invoke
        unsigned char args[1];   //beginning of the data area for function arguments
    } ClosureStruct;

    Poi creiamo una funzione che consente di inizializzare una chiusura in area di dati.

    ClosureStruct * beginClosure(void(*pf)(), int nSize, void *pArea)
    {
        ClosureStruct *p = pArea;
    
        if (p) {
            p->nBytes = 0;      //number of bytes of the data area in use
            p->nSize = nSize - sizeof(ClosureStruct);   //max size of the data area
            p->pf = pf;         //pointer to the function to invoke
        }
    
        return p;
    }

    Questa funzione è progettata per accettare un puntatore ad una zona di dati che dà la flessibilità di come l’utente della funzione vuole gestire la memoria. Si può utilizzare un po ‘ di memoria sullo stack o memoria statica o possono utilizzare la memoria heap tramite il malloc() funzione.

    unsigned char closure_area[512];
    ClosureStruct *p = beginClosure (xFunc, 512, closure_area);

    o

    ClosureStruct *p = beginClosure (xFunc, 512, malloc(512));
    // do things with the closure
    free (p);  //free the malloced memory.

    Prossimo forniamo una funzione che ci consente di aggiungere dati e argomenti per la nostra chiusura. Lo scopo di questa funzione è quello di costruire la chiusura di dati in modo che quando la chiusura viene richiamata la funzione, la funzione di chiusura sarà fornito dati necessari per fare il suo lavoro.

    ClosureStruct * pushDataClosure(ClosureStruct *p, size_t size, ...)
    {
        if (p && p->nBytes + size < p->nSize) {
            va_list jj;
    
            va_start(jj, size);    //get the address of the first argument
    
            memcpy(p->args + p->nBytes, jj, size);  //copy the specified size to the closure memory area.
            p->nBytes += size;     //keep up with how many total bytes we have copied
            va_end(jj);
        }
    
        return p;
    }

    E per fare questo un po ‘ più semplice da utilizzare, consente di fornire una confezione macro che è generalmente utile, ma ha dei limiti in quanto è C Processore manipolazione del testo.

    #define PUSHDATA(cs,d) pushDataClosure((cs),sizeof(d),(d))

    così si potrebbe usare qualcosa come il seguente codice sorgente:

    unsigned char closurearea[256];
    int  iValue = 34;
    
    ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, closurearea), iValue);
    dd = PUSHDATA(dd, 68);
    execClosure(dd);

    Invocando la Chiusura: Il execClosure() Funzione

    L’ultimo pezzo di questo è il execClosure() funzione per eseguire la funzione di chiusura, con i suoi dati. Quello che stiamo facendo in questa funzione è quello di copiare l’elenco degli argomenti forniti alla chiusura della struttura di dati nello stack come abbiamo richiamare la funzione.

    Ciò che facciamo è il cast args zona della chiusura di dati a un puntatore ad una struct contenente un unsigned char array e poi dereferenziare il puntatore in modo che il compilatore C per aggiungere una copia di argomenti in pila prima di chiamare la funzione di chiusura.

    Per rendere più facile per creare il execClosure() funzione, ci sarà creare una macro che lo rende facile creare le varie dimensioni delle strutture di cui abbiamo bisogno.

    //helper macro to reduce type and reduce chance of typing errors.
    
    #define CLOSEURESIZE(p,n)  if ((p)->nBytes < (n)) { \
    struct {\
    unsigned char x[n];\
    } *px = (void *)p->args;\
    p->pf(*px);\
    }

    Quindi usiamo questa macro per creare una serie di test per determinare come chiamare la funzione di chiusura. Le dimensioni scelte qui possono avere bisogno di tweaking per applicazioni particolari. Queste dimensioni sono arbitrari e dopo la chiusura di dati raramente possono essere delle stesse dimensioni, questo non è efficiente utilizzo dello spazio dello stack. E c’è la possibilità che ci possa essere più di chiusura dati che ci hanno consentito.

    //execute a closure by calling the function through the function pointer
    //provided along with the created list of arguments.
    ClosureStruct * execClosure(ClosureStruct *p)
    {
        if (p) {
            //the following structs are used to allocate a specified size of
            //memory on the stack which is then filled with a copy of the
            //function argument list provided in the closure data.
            CLOSEURESIZE(p,64)
            else CLOSEURESIZE(p, 128)
            else CLOSEURESIZE(p, 256)
            else CLOSEURESIZE(p, 512)
            else CLOSEURESIZE(p, 1024)
            else CLOSEURESIZE(p, 1536)
            else CLOSEURESIZE(p, 2048)
        }
    
        return p;
    }

    Ci restituiscono il puntatore alla chiusura, al fine di rendere più facilmente disponibili.

    Un Esempio di Utilizzo della Libreria Sviluppata

    Possiamo utilizzare il sopra come segue. Prima un paio di esempi di funzioni che in realtà non fare molto.

    int zFunc(int i, int j, int k)
    {
        printf("zFunc i = %d, j = %d, k = %d\n", i, j, k);
        return i + j + k;
    }
    
    typedef struct { char xx[24]; } thing1;
    
    int z2func(thing1 a, int i)
    {
        printf("i = %d, %s\n", i, a.xx);
        return 0;
    }

    Prossimo costruiamo le nostre chiusure e la loro esecuzione.

    {
        unsigned char closurearea[256];
        thing1 xpxp = { "1234567890123" };
        thing1 *ypyp = &xpxp;
        int  iValue = 45;
    
        ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
        free(execClosure(PUSHDATA(dd, iValue)));
    
        dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
        dd = PUSHDATA(dd, 68);
        execClosure(dd);
    
        dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
        dd = PUSHDATA(dd, 145);
        dd = PUSHDATA(dd, 185);
        execClosure(dd);
    }

    Che dà un output di

    i = 45, 1234567890123
    i = 68, 1234567890123
    zFunc i = 45, j = 145, k = 185

    Beh Che Dire Currying?

    Prossimo si potrebbe fare una modifica per la chiusura di una struttura per permettere a noi di fare accattivarsi le funzioni.

    typedef struct {
        size_t  nBytes;    //current number of bytes of data
        size_t  nSize;     //maximum size of the data area
        size_t  nCurry;    //last saved nBytes for curry and additional arguments
        void(*pf)();       //pointer to the function to invoke
        unsigned char args[1];   //beginning of the data area for function arguments
    } ClosureStruct;

    con il supporto di funzioni per accattivarsi e ripristino di un curry punto

    ClosureStruct *curryClosure(ClosureStruct *p)
    {
        p->nCurry = p->nBytes;
        return p;
    }
    ClosureStruct *resetCurryClosure(ClosureStruct *p)
    {
        p->nBytes = p->nCurry;
        return p;
    }

    Il codice sorgente per il test di questo potrebbe essere:

    {
        unsigned char closurearea[256];
        thing1 xpxp = { "1234567890123" };
        thing1 *ypyp = &xpxp;
        int  iValue = 45;
    
        ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
        free(execClosure(PUSHDATA(dd, iValue)));
        dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
        dd = PUSHDATA(dd, 68);
        execClosure(dd);
        dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
        dd = PUSHDATA(dd, 145);
        dd = curryClosure(dd);
        dd = resetCurryClosure(execClosure(PUSHDATA(dd, 185)));
        dd = resetCurryClosure(execClosure(PUSHDATA(dd, 295)));
    }

    con l’uscita di

    i = 45, 1234567890123
    i = 68, 1234567890123
    zFunc i = 45, j = 145, k = 185
    zFunc i = 45, j = 145, k = 295
  6. 1

    Su questa pagina si può trovare una descrizione su come fare le chiusure in C:

    http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/

    L’idea è che una struttura è necessario e struct che contiene il puntatore a funzione, ma viene fornito alla funzione come primo argomento. A parte il fatto che richiede un sacco di caldaia, il codice identificativo e la gestione della memoria è un problema, questo funziona e fornisce la potenza e la possibilità di altre lingue’ chiusure.

Lascia un commento