web-dev-qa-db-fra.com

Y a-t-il un moyen d’obtenir des fermetures en C

Je voudrais que cela fonctionne, mais cela ne fonctionne pas:

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

En fait, fait compile et fonctionne pour 1 instance ... mais la seconde échoue. Une idée de comment obtenir des fermetures en C?

Ce serait vraiment génial!

29
kristopolous

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

Mais généralement, en C, vous finissez par passer des arguments supplémentaires autour de fausses fermetures.


Apple a une extension non standard au C appelée blocks , qui fonctionne beaucoup comme des fermetures.

16
ephemient

GCC et clang ont l'extension des blocs, qui sont essentiellement des fermetures en C.

7
arsenm

La norme ANSI C ne prend pas en charge la fermeture, ainsi que les fonctions imbriquées. La solution de contournement est l'utilisation simple "struct".

Exemple simple de fermeture pour additionner deux nombres.

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

Usage:

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

Résultat:

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

Sur C++ 11, il sera atteint en utilisant l'expression 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);
}

Un résultat, après compilation avec un indicateur -std = c ++ 11.

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

GCC prend en charge les fonctions internes, mais pas les fermetures. C++ 0x aura des fermetures. Aucune version de C que je sache, et certainement pas de version standard, offre un niveau aussi impressionnant.

Phoenix , qui fait partie de Boost, fournit des fermetures en C++.

2
outis

Sur cette page, vous trouverez une description de la procédure de fermeture en C:

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

L'idée est qu'une structure est nécessaire et que cette structure contient le pointeur de la fonction, mais est fournie à la fonction en tant que premier argument. Mis à part le fait que cela nécessite beaucoup de code de plaque de chaudière et que la gestion de la mémoire est bien sûr un problème, cela fonctionne et fournit la puissance et les possibilités des fermetures d'autres langues.

1
user3761804

Définition opérationnelle d'une fermeture avec un exemple JavaScript

Une fermeture est un type d'objet qui contient un pointeur ou une référence quelconque vers une fonction à exécuter, ainsi qu'une instance des données nécessaires à la fonction.

Un exemple en JavaScript de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures est

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

qui pourrait alors être utilisé comme:

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

Certains des obstacles à surmonter avec C

Le langage de programmation C est un langage typé de manière statique, contrairement à JavaScript, il ne dispose pas non plus d'une fonction de récupération des ordures, ni d'autres fonctionnalités facilitant les fermetures en JavaScript ou dans d'autres langages avec prise en charge intrinsèque des fermetures.

Un obstacle important pour les fermetures dans la norme C est le manque de prise en charge linguistique du type de construction de l'exemple JavaScript dans lequel la fermeture inclut non seulement la fonction, mais également une copie des données capturées lors de la création de la fermeture, une manière de save state qui peut ensuite être utilisé lors de l’exécution de la fermeture avec les arguments supplémentaires fournis lors de l’appel de la fonction de fermeture.

Cependant, C a quelques éléments de base qui peuvent fournir les outils nécessaires pour créer une sorte de fermeture. Certaines des difficultés sont (1) la gestion de la mémoire incombe au programmeur, aucun ramassage des ordures, (2) les fonctions et les données sont séparées, aucune classe ou mécanisme de type de classe, (3) typé de manière statique, donc pas de découverte des types de données à l'exécution ou la taille des données, et (4) de mauvaises capacités linguistiques pour la capture des données d’état au moment de la création de la fermeture.

Une chose qui rend possible une fonction de fermeture avec C est le pointeur void * et l’utilisation de unsigned char comme sorte de type de mémoire à usage général qui est ensuite transformé en d’autres types par le biais de la conversion.

Une implémentation avec le standard C et un peu d'étirement ici et là

NOTE: L'exemple suivant dépend d'une convention de transmission d'argument basée sur la pile, comme c'est le cas avec la plupart des compilateurs x86 32 bits. La plupart des compilateurs permettent également de spécifier une convention d'appel autre que la transmission d'argument basée sur la pile. en tant que modificateur __fastcall de Visual Studio. Par défaut, Visual Studio x64 et 64 bits consiste à utiliser la convention __fastcall par défaut afin que les arguments de la fonction soient transmis dans des registres et non sur la pile. Voir Vue d'ensemble des conventions d'appel x64 in Microsoft MSDN ainsi que Comment définir les arguments de fonction dans Assembly lors de l'exécution dans une application 64 bits sous Windows? ainsi que les différentes réponses et commentaires dans Comment les arguments variables sont-ils implémentés dans gcc? .

Une chose que nous pouvons faire est de résoudre ce problème de fournir une sorte d’installation de fermeture pour C est de simplifier le problème. Mieux vaut fournir une solution à 80% utile pour la majorité des applications que pas de solution du tout.

L'une de ces simplifications consiste à ne prendre en charge que les fonctions qui ne renvoient pas de valeur, autrement dit les fonctions déclarées comme void func_name(). Nous allons également abandonner la vérification du type à la compilation de la liste des arguments de la fonction puisque cette approche construit la liste des arguments de la fonction au moment de l'exécution. Aucune de ces choses que nous abandonnons n’est triviale, la question est donc de savoir si la valeur de cette approche des fermetures en C l'emporte sur ce que nous abandonnons.

Tout d’abord, définissons notre zone de données de fermeture. La zone de données de fermeture représente la zone de mémoire que nous allons utiliser pour contenir les informations nécessaires à une fermeture. Le minimum de données auquel je peux penser est un pointeur sur la fonction à exécuter et une copie des données à fournir à la fonction sous forme d'arguments.

Dans ce cas, nous allons fournir toutes les données d'état capturées nécessaires à la fonction en tant qu'argument de la fonction.

Nous voulons également mettre en place des protections de base afin d’échouer de manière raisonnable en toute sécurité. Malheureusement, les rails de sécurité sont un peu faibles avec certains des contournements que nous utilisons pour mettre en place une forme de fermeture.

Le code source

Le code source suivant a été développé à l'aide de Visual Studio 2017 Community Edition dans un fichier source .c.

La zone de données est une structure contenant des données de gestion, un pointeur sur la fonction et une zone de données ouverte.

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;

Ensuite, nous créons une fonction qui initialisera une zone de données de fermeture.

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

Cette fonction est conçue pour accepter un pointeur sur une zone de données, ce qui offre une flexibilité quant à la manière dont l'utilisateur de la fonction veut gérer la mémoire. Ils peuvent soit utiliser de la mémoire sur la pile, soit de la mémoire statique, soit utiliser de la mémoire heap via la fonction malloc().

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

ou

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

Ensuite, nous fournissons une fonction qui nous permet d’ajouter des données et des arguments à notre clôture. Le but de cette fonction est de construire les données de fermeture de sorte que, lorsque la fonction de fermeture est invoquée, toutes les données nécessaires à son travail soient fournies à la fonction de fermeture.

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

Et pour rendre cela un peu plus simple à utiliser, fournissons une macro d’emballage qui est généralement pratique, mais qui a ses limites car il s’agit d’une manipulation de texte en C Processor.

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

nous pourrions alors utiliser quelque chose comme le code source suivant:

unsigned char closurearea[256];
int  iValue = 34;

ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, closurearea), iValue);
dd = PUSHDATA(dd, 68);
execClosure(dd);

Invocation de la fermeture: La fonction execClosure ()

La dernière étape est la fonction execClosure() pour exécuter la fonction de fermeture avec ses données. Ce que nous faisons dans cette fonction est de copier la liste d'arguments fournie dans la structure de données de fermeture sur la pile lorsque nous appelons la fonction.

Ce que nous faisons est de convertir la zone args des données de fermeture en un pointeur sur une structure contenant un tableau unsigned char, puis de déréférencer le pointeur de sorte que le compilateur C place une copie des arguments sur la pile avant d'appeler la fonction dans la fermeture. .

Pour faciliter la création de la fonction execClosure(), nous allons créer une macro qui facilite la création des différentes tailles de struct dont nous avons besoin.

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

Nous utilisons ensuite cette macro pour créer une série de tests afin de déterminer comment appeler la fonction de fermeture. Les tailles choisies ici peuvent nécessiter une mise au point pour des applications particulières. Ces tailles sont arbitraires et, étant donné que les données de fermeture auront rarement la même taille, l'utilisation de l'espace de pile n'est pas efficace. Et il est possible qu'il y ait plus de données de fermeture que nous en avons prévues.

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

Nous renvoyons le pointeur sur la fermeture afin de le rendre facilement disponible.

Un exemple utilisant la bibliothèque développée

Nous pouvons utiliser ce qui précède comme suit. Tout d'abord, quelques exemples de fonctions qui ne font pas grand chose.

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

Ensuite, nous construisons nos fermetures et les exécutons.

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

Ce qui donne une sortie de

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

Eh bien, qu'en est-il du currying?

Ensuite, nous pourrions apporter une modification à notre structure de fermeture pour nous permettre d’assumer le currying de fonctions.

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;

avec les fonctions de support pour le curry et la réinitialisation d'un point de curry

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

Le code source pour tester cela pourrait être:

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

avec la sortie de 

i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
zFunc i = 45, j = 145, k = 295
0
Richard Chambers