2017-08-16 1 views
3

J'ai un morceau de code qui ressemble presque à ce (ce qui est simplifié juste pour montrer la structure):Le bon modèle pour la séquence des allocations et des opérations qui peut échouer

int Ugly_Calculation(void) 
{ 
    type1 *X1 = type1_new(); 
    if (!X1) return 0; 
    type2 *X2 = type2_new(X1); 
    if (!X2) return 0; 
    type3 *X3 = type3_new(X1, X2); 
    if (!X3) return 0; 
    int result = PerformCalculation(X1, X2, X3); 
    free(X3); 
    free(X2); 
    free(X1); 
    return result; 
} 

Il alloue des objets et usages les pour un calcul et libère les objets. Les fonctions typeX_new retournent souvent zéro, donc la mémoire n'est généralement pas libérée correctement.

Dans ma première itération, j'ai réécrit le code de manière suivante:

typedef struct 
{ 
    type1 *X1; 
    type2 *X2; 
    type3 *X3; 
} ugly_context; 

void free_ctx(ugly_context *ctx) 
{ 
    free(ctx->X1); 
    free(ctx->X2); 
    free(ctx->X3); 
} 

int Ugly_Calculation(void) 
{ 
    ugly_context ctx = {NULL, NULL, NULL}; 

    ctx->X1 = type1_new(); 
    if (!ctx->X1) 
    { 
     free_ctx(&ctx); 
     return 0; 
    } 
    ctx->X2 = type1_new(ctx->X1); 
    if (!ctx->X2) 
    { 
     free_ctx(&ctx); 
     return 0; 
    } 
    ctx->X3 = type1_new(ctx->X1, ctx->X2); 
    if (!ctx->X3) 
    { 
     free_ctx(&ctx); 
     return 0; 
    } 
    int result = PerformCalculation(X1, X2, X3); 
    free_ctx(&ctx); 
    return result; 
} 

Ma première solution est sûre, mais il a perdu la lisibilité du code d'origine. Ma deuxième idée suit:

type2 *type2_new_check(type1 *X1) 
{ 
    type2 *result = NULL; 
    if (X1) 
    { 
     result = type2_new(X1); 
    } 
    return result; 
} 

type3 *type3_new_check(type1 *X1, type2 *X2) 
{ 
    type3 *result = NULL; 
    if (X1 && X2) 
    { 
     result = type3_new(X1, X2); 
    } 
    return result; 
} 

int PerformCalculation_check(type1 *X1, type2 *X2, type3 *X3) 
{ 
    int result = 0; 
    if (X1 && X2 && X3) 
    { 
     result = PerformCalculation(X1, X2, X3); 
    } 
    return result; 
} 

int Ugly_Calculation(void) 
{ 
    type1 *X1 = type1_new(); 
    type2 *X2 = type2_new_check(X1); 
    type3 *X3 = type3_new_check(X1, X2); 
    int result = PerformCalculation_check(X1, X2, X3); 
    free(X3); 
    free(X2); 
    free(X1); 
    return result; 
} 

La lisibilité du code est OK et sorties début ont disparu, mais il est assez long pour la méthode avec 3 blocs de mémoire.

Mon vrai code n'est pas 3 allocations, mais 15, donc la méthode devient très effrayante et moche. Yat-il un moyen agréable, comment résoudre ce modèle sans contrôles excessifs et libérer? (Le mieux serait de briser la méthode des parties plus petites qui sont proprement gérables par des contrôles, mais je voudrais éviter que, pour une raison quelconque)

Répondre

3

C'est (à mon humble avis) un bon usage pour goto (et idiome commun C):

type *obj1 = 0; 
    type *obj2 = 0; 
    type *obj3 = 0; 

    if (!(obj1 = create1())) goto error; 
    if (!(obj2 = create2())) goto error; 
    if (!(obj3 = create3())) goto error; 

    // your logic here 

    return 0; // success 

error: 
    free(obj3); 
    free(obj2); 
    free(obj1); 
    return -1; // failure 

Ou, si vous avez besoin de libérer les ressources allouées en cas de succès aussi bien, comme celui-ci:

type *obj1 = 0; 
    type *obj2 = 0; 
    type *obj3 = 0; 
    int success = -1; 

    if (!(obj1 = create1())) goto error; 
    if (!(obj2 = create2())) goto error; 
    if (!(obj3 = create3())) goto error; 

    success = 0; // success 

    // your logic here 

error: 
    free(obj3); 
    free(obj2); 
    free(obj1); 
    return success; 
+0

Cela ne permettra pas à la plupart des compilateurs de rester heureux, mais de laisser les analyseurs statiques. Ils ont tendance à ne pas aimer l'assignation à l'intérieur des conditions et 'goto' à la fois. Bien que ce code soit correct, je crois qu'il existe de meilleurs moyens. – Lundin

+0

une affectation paranthésisée est bien pour les compilateurs que je connais, et 'goto' est bien sûr aussi. –

+0

Cependant, aucun d'eux ne passera devant un vérificateur MISRA-C. – Lundin

1

La meilleure façon de gérer ce qui est souvent d'utiliser un couple des fonctions internes de l'encapsuleur. Gardez l'allocation séparée de l'algorithme. Initialiser les variables à 0 (NULL) et tirer parti de free(NULL) étant un no-op bien défini, ce qui signifie que tout sera nettoyé, peu importe la fonction qui a échoué.

static int allocate (type1** X1, type2** X2, type3** X3) 
{ 
    *X1 = type1_new(); if(*X1 == NULL) return 0; 
    *X2 = type2_new(); if(*X2 == NULL) return 0; 
    *X3 = type3_new(); if(*X3 == NULL) return 0; 
    return 1; 
} 

int Ugly_Calculation (void) 
{ 
    type1* X1 = 0; 
    type2* X2 = 0; 
    type3* X3 = 0; 

    int result = allocate(&X1, &X2, &X3); 
    if(result != 0) 
    { 
    result = PerformCalculation(X1, X2, X3); 
    } 

    free(X1); 
    free(X2); 
    free(X3); 

    return result; 
} 
+0

Un autre énorme avantage de ceci est que vous n'avez pas encore à supporter le débat 'goto' ... – Lundin

+0

Je ne pense pas qu'il y ait beaucoup de débat pour utiliser' goto' dans exactement ce scénario. Mais si vous voulez l'éviter, peu importe quoi, c'est probablement le moyen le plus simple. –

+0

@FelixPalmen Donnez-leur quelques heures et le débat commencera. N'as-tu pas entendu les nouvelles? [GoTo considéré comme nuisible] (http://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf)! :) – Lundin