2017-02-03 1 views
4

La question est: Pourriez-vous s'il vous plaît m'aider à mieux comprendre la macro RAII en langage C (pas C++) en utilisant uniquement les ressources que je fournis au bas de cette question? J'essaie de l'analyser dans mon esprit afin de comprendre ce qu'il dit et comment cela a du sens (cela n'a pas de sens dans mon esprit). La syntaxe est difficile. L'objectif de la question est: j'ai du mal à lire et à comprendre la syntaxe bizarre et sa mise en œuvre en langage C. Par exemple, je peux facilement lire, comprendre et analyser (il est logique pour moi) la macro swap suivante:L'acquisition des ressources est l'initialisation en langage C

#define myswap(type,A,B) {type _z; _z = (A); (A) = (B); (B) = _z;} 

(le passage suivant est soulevé du livre: Comprendre les pointeurs C)

En langage C, le compilateur GNU fournit une extension non standard à support RAII.

L'extension GNU utilise une macro appelée RAII_VARIABLE. Il déclare une variable et associés à la variable:

  • Un type
  • Une fonction à exécuter lorsque la variable est créée
  • Une fonction à exécuter lorsque la variable est hors de portée

    La macro est illustré ci-dessous:

    #define RAII_VARIABLE(vartype,varname,initval,dtor) \ 
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \ 
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) 
    

    Exemple:

    void raiiExample() { 
    RAII_VARIABLE(char*, name, (char*)malloc(32), free); 
    strcpy(name,"RAII Example"); 
    printf("%s\n",name); 
    } 
    
    int main(void){ 
        raiiExample(); 
    } 
    

Lorsque cette fonction est exécutée, la chaîne « RAII_Example » sera affiché. Des résultats similaires peuvent être obtenus sans utiliser l'extension GNU.

+0

Je n'aime pas cette méthode, cela ne ressemble pas C. – Stargateur

+2

Pourquoi utiliser cette macro au lieu de ... en utilisant C++? –

+1

L'objectif de la question est: Pourriez-vous s'il vous plaît m'aider à mieux comprendre la macro RAII en langage C? Je suis aux prises avec la syntaxe et la façon dont elle est implémentée dans Clang. Je sais ce qu'il fait (alloue et libère la mémoire .....). :) – Mynicks

Répondre

2

Bien sûr, vous pouvez réaliser n'importe quoi sans utiliser RAII. RAII l'utilise pour ne pas avoir à penser explicitement à libérer des ressources. Un modèle comme:

void f() { 
    char *v = malloc(...); 
    // use v 
    free v; 
} 

besoin de prendre soin de libérer de la mémoire, sinon vous auriez une fuite de mémoire. Comme il est pas toujours facile de libérer correctement ressources, RAII vous offre un moyen Automatiser la libération:

void f() { 
    RAII_VARIABLE(char*, v, malloc(...), free); 
    // use v 
} 

Ce qui est intéressant est que sera publié ressource quel que soit le chemin d'exécution sera. Donc, si votre code est une sorte de code spaghetti, plein de conditions et de tests complexes, etc, RAII vous permet de libérer votre esprit de libérer ...

2

La syntaxe est un peu difficile, car __attribute__ ((cleanup)) s'attend à passer une fonction qui prend le pointeur sur la variable. De GCC documentation (Souligné par l'auteur):

La fonction doit prendre un paramètre, un pointeur à un type compatible avec la variable. La valeur de retour de la fonction (le cas échéant) est ignorée.

Tenir compte suivant incorrect exemple:

char *name __attribute__((cleanup(free))) = malloc(32); 

Il serait beaucoup plus simple à mettre en œuvre comme ça, mais dans ce cas la fonction free implicitement prend pointeur-name, où son type est char ** . Vous avez besoin d'un moyen de forcer le passage de l'objet approprié, ce qui est l'idée même de la macro RAII_VARIABLE.

L'incarnation simplifiée et non générique du RAII_VARIABLE serait de définir la fonction, par exemple raii_free:

#include <stdlib.h> 

void raii_free(char **var) { free(*var); } 

int main(void) 
{ 
    char *name __attribute__((cleanup(raii_free))) = malloc(32); 
    return 0; 
} 
3

Ok, regardons les parties de la ligne macro en ligne

#define RAII_VARIABLE(vartype,varname,initval,dtor) \ 

Cette première ligne est, bien sûr, le nom de la macro plus sa liste d'arguments. Rien d'inattendu ici, nous semblons passer un type, un nom de jeton, une expression à init une variable, et un destructeur qui, espérons-le, sera appelé à la fin. Jusqu'à présent, si facile.

void _dtor_ ## varname (vartype * v) { dtor(*v); } \ 

La deuxième ligne déclare une fonction. Il prend le jeton fourni varname et ajoute le préfixe _dtor_ (l'opérateur ## ordonne au préprocesseur de fusionner les deux jetons en un seul jeton). Cette fonction prend un pointeur sur vartype en tant qu'argument et appelle le destructeur fourni avec cet argument.

Cette syntaxe peut être inattendue ici (comme l'utilisation de l'opérateur ##, ou le fait qu'il repose sur la possibilité de déclarer des fonctions imbriquées), mais ce n'est pas encore vraiment magique. La magie apparaît sur la troisième ligne:

vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) 

Ici la variable est déclarée, sans __attribute__() cela semble assez straight-forward: vartype varname = (initvar). La magie est la directive __attribute__((cleanup(_dtor_ ## varname))). Il ordonne au compilateur de s'assurer que la fonction fournie est appelée lorsque la variable tombe hors de la portée.


La syntaxe est __attribute__() est une extension du langage fourni par le compilateur, donc vous êtes profondément dans le comportement défini de mise en œuvre ici. Vous ne pouvez pas compter sur d'autres compilateurs fournissant le même __attribute__((cleanup())). Beaucoup peuvent le fournir, mais aucun ne le doit. Certains anciens compilateurs peuvent même ne pas connaître la syntaxe __attribute__(), auquel cas la procédure standard est #define __attribute__() vide, en supprimant toutes les déclarations __attribute__() du code. Vous ne voulez pas que cela arrive avec les variables RAII. Donc, si vous comptez sur un __attribute__(), sachez que vous avez perdu la possibilité de compiler avec n'importe quel compilateur conforme standard.