2009-08-02 5 views
13

Je viens d'un arrière-plan en C#, mais j'apprends le C en ce moment. En C#, quand on veut signaler qu'une erreur s'est produite, on lance une exception. Mais qu'est-ce que tu fais en C? Par exemple, vous avez une pile avec push et pop. Quel est le meilleur moyen de signaler que la pile est vide pendant un pop? Que revenez-vous de cette fonction?C: Comment simulez-vous une 'exception'?

double pop(void) 
{ 
    if(sp > 0) 
     return val[--sp]; 
    else { 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 
} 

K&R l » exemple de la page 77 (le code ci-dessus ) renvoie un 0.0. Mais que faire si l'utilisateur a poussé un 0.0 plus tôt sur la pile, comment savoir si la pile est vide ou si une valeur correcte a été retournée?

Répondre

15

comportement comme exception dans C est réalisée par l'intermédiaire setjmp/longjmp. Cependant, ce que vous voulez vraiment ici est un code d'erreur. Si toutes les valeurs sont potentiellement réutilisables, alors vous voudrez peut-être prendre dans un hors-paramètre comme un pointeur, et l'utiliser pour retourner la valeur, comme suit:

int pop(double* outval) 
{ 
     if(outval == 0) return -1; 
     if(sp > 0) 
       *outval = val[--sp]; 
     else { 
       printf("error: stack empty\n"); 
       return -1; 
     } 
     return 0; 
} 

Pas idéal, évidemment, mais ce sont les limites En outre, si vous suivez cette route, vous pouvez définir des constantes symboliques pour vos codes d'erreur (ou utiliser the standard ones), de sorte qu'un utilisateur puisse faire la distinction entre "pile vide" et "vous m'avez donné" un pointeur nul, abruti ".

+2

Je ne suis pas d'accord, car même si je comprends ce que vous voulez dire, je ne donnerais pas à quelqu'un venant de java/C# land la supposition que setjmp/longjmp est en quelque sorte la 'solution' où est mon exception? – Jonke

+0

Notez que très peu de code C utilise setjmp/longjmp pour des raisons historiques. Les codes d'erreur sont les habituels. Les codes d'erreur ne sont pas très bons; le manque d'exceptions en C est une raison majeure pour laquelle les langues plus modernes sont meilleures. – Nelson

+0

ANSI a codifié 'setjmp 'donc il est garanti de fonctionner (au moins si le compilateur est conforme à la norme), mais le commutateur de pile et la simultanéité n'ont pas été standardisés, donc il est toujours un problème pour écrire un paquet pour C (même en utilisant asm pour faire le changement de contexte) car un compilateur * pourrait * (bien que cela puisse être improbable) effectuer des optimisations et transforme qui casse les suppositions dans le paquetage des threads. – Jonke

4

Une approche consiste à spécifier que pop() a un comportement indéfini si la pile est vide. Vous devez ensuite fournir une fonction is_empty() qui peut être appelée pour vérifier la pile.

Une autre approche consiste à utiliser C++, ce qui fait des exceptions :-)

+0

plus utile pour ce cas particulier, C++ a une pile là dans la bibliothèque :-) –

+0

Mais le C++ std :: pile pop la fonction ne fait pas réellement ce que le PO veut. –

+1

Vrai, et comprendre pourquoi va ajouter à l'éducation C++ de l'OP, ce qui est le but principal de la question :-) Quoi qu'il en soit, enrouler un appel chacun à 'top()' et 'pop()', retourner une copie, donne le le même résultat final que de prendre ce que le PO a et d'appliquer ce que vous dites sur le besoin d'une fonction 'empty()'. IYSWIM. –

1

vous pouvez retourner un pointeur vers double:

  • non NULL -> valide
  • NULL -> invalide
+0

les sous-critiques sans commentaires sont inutiles, veuillez expliquer vos downvotes – dfa

+3

Je ne suis pas la downvote, mais je me demande d'où vient le stockage de sauvegarde pour le pointeur. Si c'est l'élément sauté, alors l'appelant doit le déréférencer avant qu'une nouvelle valeur puisse être poussée. Si c'est un double statique, alors l'appelant doit déréférencer avant le prochain appel à pop. Les deux causent beaucoup de problèmes pour le code d'appel. – RBerteig

+0

Je n'ai pas downvote non plus mais j'ai eu les mêmes préoccupations. Je pense que c'est une approche efficace, mais vous devez changer la façon dont la fonction fonctionne et stocke les données pour le faire. – Jon

7

Vous avez quelques options:

1) Magic valeur d'erreur. Pas toujours assez bon, pour la raison que vous décrivez. Je suppose qu'en théorie pour ce cas vous pourriez retourner un NaN, mais je ne le recommande pas.

2) Définir qu'il n'est pas valide de sauter lorsque la pile est vide. Alors votre code suppose soit qu'il n'est pas vide (et qu'il ne soit pas défini si c'est le cas), soit qu'il l'affirme.

3) Modifier la signature de la fonction afin que vous puissiez indiquer le succès ou l'échec.

int pop(double *dptr) 
{ 
    if(sp > 0) { 
      *dptr = val[--sp]; 
      return 0; 
    } else { 
      return 1; 
    } 
} 

document comme « En cas de succès, renvoie 0 et écrit la valeur à l'emplacement pointé par dptr sur échec, renvoie une valeur non nulle. "

Facultativement, vous pouvez utiliser la valeur de retour ou errno pour indiquer la raison de l'échec, bien que pour cet exemple particulier, il n'y ait qu'une seule raison.

4) Passer un objet "exception" dans chaque fonction par pointeur, et lui écrire une valeur en cas d'échec. L'appelant le vérifie ou non en fonction de la manière dont il utilise la valeur de retour. Cela ressemble beaucoup à l'utilisation de "errno", mais sans qu'il s'agisse d'une valeur à l'échelle du thread.

5) Comme d'autres l'ont dit, implémentez des exceptions avec setjmp/longjmp. C'est faisable, mais nécessite soit de passer un paramètre supplémentaire partout (la cible du longjmp à effectuer en cas d'échec), soit de le cacher dans les globales. Cela rend également la gestion des ressources de style C typique un cauchemar, car vous ne pouvez pas appeler un élément qui pourrait dépasser le niveau de votre pile si vous détenez une ressource dont vous êtes responsable.

+0

+1: Pour le point # 3. –

10

Vous pouvez créer un système d'exception au-dessus de longjmp/setjmp: Exceptions in C with Longjmp and Setjmp. Cela fonctionne plutôt bien, et l'article est bien lu. Voici comment votre code pourrait ressembler si vous avez utilisé le système d'exception de l'article lié:

TRY { 
    ... 
    THROW(MY_EXCEPTION); 
    /* Unreachable */ 
    } CATCH(MY_EXCEPTION) { 
    ... 
    } CATCH(OTHER_EXCEPTION) { 
    ... 
    } FINALLY { 
    ... 
    } 

Il est incroyable ce que vous pouvez faire avec un peu de macros, non? Il est tout aussi incroyable de voir à quel point il est difficile de comprendre ce qui se passe si vous ne savez pas déjà ce que font les macros. Longjmp/setjmp sont portables: C89, C99 et POSIX.1-2001 spécifient setjmp(). Notez cependant que les exceptions implémentées de cette manière auront toujours des limitations par rapport aux exceptions "réelles" en C# ou en C++. Un problème majeur est que seul votre code sera compatible avec ce système d'exception. Comme il n'y a pas de norme établie pour les exceptions dans C, les bibliothèques système et tierces ne fonctionneront pas de manière optimale avec votre système d'exception. Pourtant, cela peut parfois s'avérer être un hack utile.

Je ne recommande pas d'utiliser ceci dans le code sérieux auquel les programmeurs autres que vous-même sont censés travailler. C'est trop facile de vous tirer dessus avec ça si vous ne savez pas exactement ce qui se passe. Le filetage, la gestion des ressources et la gestion des signaux sont des zones problématiques que les programmes non-jouets rencontreront si vous essayez d'utiliser des "exceptions" longjmp.

+4

J'ai effectivement construit quelque chose comme ça en C++ avant que les exceptions deviennent largement disponibles. J'ai même mis en œuvre par propre forme de déroulement de la pile. Heureusement, je suis revenu à la raison avant de l'utiliser dans le code de production. –

+0

@Neil: J'ai aimé la deuxième partie de cette phrase :-) – jeroenh

+1

"Heureusement, je suis venu à mes sens". Toutes nos félicitations. Symbian a fait la même chose que vous, jusqu'au moment où vous êtes arrivé à vos sens, et ils ont expédiés. 10+ ans plus tard, ils ont toujours NewLC partout ... –

2

Il n'y a pas d'équivalent à des exceptions en C. Vous devez concevoir votre signature de fonction pour renvoyer des informations d'erreur, si c'est ce que vous voulez.

Les mécanismes disponibles en C sont:

  • GOTO non locale avec setjmp/longjmp
  • Signaux

Cependant, aucun d'entre eux a une sémantique ressemblant à distance C# (ou C++) exceptions .

3

Dans des cas comme cela, vous faites habituellement l'un des

  • Laisser à l'appelant. par exemple. il appartient à l'appelant de savoir s'il est sûr de pop() (par exemple, appelez une fonction stack-> is_empty() avant de faire exploser la pile), et si l'appelant se trompe, c'est de sa faute et bonne chance.
  • Signaler l'erreur via un paramètre out ou une valeur de retour.

par exemple.soit vous ne

double pop(int *error) 
{ 
    if(sp > 0) { 
     return val[--sp]; 
     *error = 0; 
    } else { 
    *error = 1; 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 

}

ou

int pop(double *d) 
{ 
    if(sp > 0) { 
     *d = val[--sp]; 
     return 0; 
    } else { 
    return 1; 

    } 
} 
0

Il y a déjà quelques bonnes réponses ici, je voulais juste dire que quelque chose proche de « exception », peut être fait avec l'utilisation de une macro, comme cela a été fait dans le génial MinUnit (cela ne renvoie que l'exception à la fonction de l'appelant).

1

1) Vous renvoyez une valeur d'indicateur pour montrer qu'elle a échoué, ou vous utilisez une syntaxe TryGet où le retour est un booléen de réussite pendant que la valeur est passée à travers un paramètre de sortie.

2) Si c'est sous Windows, il existe une forme d'exception C pure au niveau du système d'exploitation, appelée Structed Exception Handling, utilisant une syntaxe comme "_try". Je le mentionne, mais je ne le recommande pas pour ce cas.

4

Ceci est en fait un exemple parfait des tentatives de surcharger le type de retour avec des valeurs magiques et simplement une conception d'interface discutable.

Une solution que je pourrais utiliser pour éliminer l'ambiguïté (et donc la nécessité d'une « exception comme le comportement ») dans l'exemple est de définir un bon type de retour:

struct stack{ 
    double* pData; 
    uint32 size; 
}; 

struct popRC{ 
    double value; 
    uint32 size_before_pop; 
}; 

popRC pop(struct stack* pS){ 
    popRC rc; 
    rc.size=pS->size; 
    if(rc.size){ 
     --pS->size; 
     rc.value=pS->pData[pS->size]; 
    } 
    return rc; 
} 

Utilisation est bien sûr:

popRC rc = pop(&stack); 
if(rc.size_before_pop!=0){ 
    ....use rc.value 

Cela arrive tout le temps, mais en C++ pour éviter de telles ambiguïtés on revient habituellement juste un

std::pair<something,bool> 

où le bool est un indicateur de succès - regarder quelques-uns des:

std::set<...>::insert 
std::map<...>::insert 

ajouter une alternative double* à l'interface et un retour (! N UNOVERLOADED) code de retour, disent un ENUM indiquant le succès.

Bien sûr, il n'était pas nécessaire de retourner la taille dans la structure popRC. Il aurait pu être

enum{FAIL,SUCCESS}; 

Mais puisque la taille pourrait servir d'indice utile à la pop'er vous pourriez aussi bien l'utiliser.

BTW, je suis entièrement d'accord que l'interface de la pile struct doit avoir

int empty(struct stack* pS){ 
    return (pS->size == 0) ? 1 : 0; 
} 
0

setjmp, longjmp et macros. Cela a été fait un certain nombre de fois — la plus ancienne mise en œuvre que je connaisse est par Eric Roberts et Mark vanderVoorde — mais celui que j'utilise actuellement fait partie de C Interfaces and Implementations de Dave Hanson et est libre de Princeton.

1

Quelque chose que personne n'a encore mentionné, il est assez laid bien:

int ok=0; 

do 
{ 
    /* Do stuff here */ 

    /* If there is an error */ 
    break; 

    /* If we got to the end without an error */ 
    ok=1; 

} while(0); 

if (ok == 0) 
{ 
    printf("Fail.\n"); 
} 
else 
{ 
    printf("Ok.\n"); 
} 
Questions connexes