3
votes

Est-il possible de renvoyer différents "faux" en C?

J'écris un wrapper pour realloc . Pour les valeurs de retour, j'utilise ceci:

if (!foo()) exit(EXIT_FAILURE);

Ceci est utilisé dans une fonction comme celle-ci:

if(salloc(a,b) { /* Do stuff */ }
else { /* Handle error */ }

Ce n'est bien sûr pas le complet code, mais il suffit de démontrer ce que je veux. Jusqu'ici tout va bien. Je peux vérifier la valeur de retour comme ceci:

if(!salloc(a,b)) // Handle error

ou si je veux être plus précis:

if(salloc(a,b) == SALLOC_OVERFLOW) // Handle overflow

Cependant, Je veux que cela soit compatible avec la méthode habituelle de vérification des erreurs. Je veux pouvoir utiliser:

if(salloc(a,b) != SALLOC_OK) // Handle error

et

salloc_rc salloc(<args>) {
    if(blabla) return SALLOC_OVERFLOW;
    if(blabla) return SALLOC_ALLOCFAIL;
    /* More error codes */
    return SALLOC_OK;
}

Le problème ici est que 0 est faux, et TOUT le reste est vrai. Ce que je veux faire semble impossible, mais il y a peut-être un moyen de contourner ce que j'ai manqué. La seule solution que j'ai trouvée jusqu'à présent est de donner à la fonction un argument supplémentaire avec un pointeur vers un endroit où je peux stocker le code d'erreur, mais je veux éviter cela si possible.

TL; DR

Comment rendre une fonction foo () capable de renvoyer différents messages d'erreur en fonction de ce qui ne va pas tout en gardant la possibilité de vérifier les erreurs de manière "traditionnelle" , comme

typedef enum { SALLOC_OK, SALLOC_OVERFLOW, SALLOC_ALLOCFAIL, ... } salloc_rc;


8 commentaires

Je préfère if (salloc (a, b)! = SALLOC_OK) . C'est plus explicite et rend votre intention plus claire. Il n'y a aucun moyen de faire en sorte que zéro signifie plus d'une chose sans rendre les choses plus complexes et moins claires que cela.


À part: Notez que lorsque realloc () renvoie NULL , cela ne signifie pas en soi que realloc () a échoué. realloc (ptr, 0) est une réponse potentielle valide (sans mémoire insuffisante). Je peux voir pourquoi vous voulez un wrapper pour realloc () .


"possible de renvoyer différents" faux "en C?" Bien sûr - renvoyez 0.0 ou -0.0, les deux sont faux et peuvent être discernés l'un de l'autre lorsque -0.0 est pris en charge - mais je ne pense pas qu'une réponse FP soit une bonne approche ici.


@chux C'est bien là, mais je veux pouvoir faire la différence entre plus de deux erreurs différentes.


@Broman 😉 Grumble, grogne les 4 prochaines seront recherchées , grognent, grognent, grognent.


@chux Vous avez tout à fait raison. Désolé pour ça. Je suppose que je vais m'excuser avec le fait que je ne m'attendais pas à ce que quelqu'un trouve une telle faille. :RÉ


@Broman Fait intéressant, un système peut prendre en charge plusieurs pointeurs nuls , tous équivalent à 0 , NULL et les uns aux autres (faux), mais avec un bit différent modèles - peuvent donc être différenciés par d'autres moyens. Et avec les anciennes machines à complément non-deux, nous avons -0, +0 entiers (tous deux faux) et différentiables par d'autres moyens. Pourtant, je soupçonne que l'approche la plus propre ici est de renvoyer un int -like error-code . 0: pas d'erreur, sinon: erreur. À découvrir au mieux avec cette bonne réponse .


Quel est le problème avec switch (salloc (a, b)) case SALLOC_OK: ... case SALLOC_OVERFLOW: ... case SALLOC_ALLOCFAIL: .... ?


3 Réponses :


1
votes

Cela se fait généralement dans le bon sens inverse.

typedef enum {
  USBD_OK   = 0,
  USBD_BUSY,
  USBD_FAIL,
}USBD_StatusTypeDef;

#define NRF_SUCCESS                           (NRF_ERROR_BASE_NUM + 0)  ///< Successful command
#define NRF_ERROR_SVC_HANDLER_MISSING         (NRF_ERROR_BASE_NUM + 1)  ///< SVC handler is missing
#define NRF_ERROR_SOFTDEVICE_NOT_ENABLED      (NRF_ERROR_BASE_NUM + 2)  ///< SoftDevice has not been enabled
#define NRF_ERROR_INTERNAL                    (NRF_ERROR_BASE_NUM + 3)  ///< Internal Error
#define NRF_ERROR_NO_MEM                      (NRF_ERROR_BASE_NUM + 4)  ///< No Memory for operation
#define NRF_ERROR_NOT_FOUND                   (NRF_ERROR_BASE_NUM + 5)  ///< Not found
#define NRF_ERROR_NOT_SUPPORTED               (NRF_ERROR_BASE_NUM + 6)  ///< Not supported
#define NRF_ERROR_INVALID_PARAM               (NRF_ERROR_BASE_NUM + 7)  ///< Invalid Parameter
#define NRF_ERROR_INVALID_STATE               (NRF_ERROR_BASE_NUM + 8)  ///< Invalid state, operation disallowed in this state
#define NRF_ERROR_INVALID_LENGTH              (NRF_ERROR_BASE_NUM + 9)  ///< Invalid Length
#define NRF_ERROR_INVALID_FLAGS               (NRF_ERROR_BASE_NUM + 10) ///< Invalid Flags
#define NRF_ERROR_INVALID_DATA                (NRF_ERROR_BASE_NUM + 11) ///< Invalid Data
#define NRF_ERROR_DATA_SIZE                   (NRF_ERROR_BASE_NUM + 12) ///< Invalid Data size
#define NRF_ERROR_TIMEOUT                     (NRF_ERROR_BASE_NUM + 13) ///< Operation timed out
#define NRF_ERROR_NULL                        (NRF_ERROR_BASE_NUM + 14) ///< Null Pointer
#define NRF_ERROR_FORBIDDEN                   (NRF_ERROR_BASE_NUM + 15) ///< Forbidden Operation
#define NRF_ERROR_INVALID_ADDR                (NRF_ERROR_BASE_NUM + 16) ///< Bad Memory Address
#define NRF_ERROR_BUSY                        (NRF_ERROR_BASE_NUM + 17) ///< Busy
#define NRF_ERROR_CONN_COUNT                  (NRF_ERROR_BASE_NUM + 18) ///< Maximum connection count exceeded.
#define NRF_ERROR_RESOURCES                   (NRF_ERROR_BASE_NUM + 19) ///< Not enough resources for operation

C'est une logique très simple - si la fonction renvoie une valeur non nulle - cela signifie que quelque chose ne va pas.

Exemples de la vie réelle: Bibliothèques STM: / * Suivant l'état du périphérique USB * /

 if(salloc(a,b)) // Handle error

 if(USB_Transmit(a,b)) // Handle error

Où NRF_ERROR_BASE_NUM est généralement 0


2 commentaires

Est-ce que cela se fait généralement de manière opposée? Au moins, ce n'est pas pour les fonctions d'allocation de mémoire. Un pointeur NULL est renvoyé en cas d'échec.


@Broman son implenemtation ne renvoie pas le pointeur seulement une énumération si je comprends bien. Ce n'est donc pas la fonction d'allocation de mémoire typique



3
votes

Si vous écrivez

salloc_rc returnCode = salloc(a, b);
if (returnCode != SALLOC_OK) handleError();

c'est tout simplement faux. Bogue grave. Mais si vous écrivez

if (salloc(a, b)) handleError();

alors, en tant que lecteur de votre code, je n'ai aucune idée si cette déclaration est correcte ou non, ce qui m'oblige à lire la documentation. Donc, la manière correcte de faire ceci est:

if (! salloc(a,b)) handleError();

C'est propre, ça dit exactement au lecteur ce qui se passe, ça donne une chance de définir un point d'arrêt où vous examinez le code de retour. Gagnez partout. N'ayez pas peur de quelques frappes supplémentaires. Rendez votre code lisible. Et si on vous parle d'une "manière habituelle de vérifier les erreurs" qui rend votre code difficile à lire, alors n'utilisez pas la "manière habituelle de vérifier les erreurs".

Notez que dans de nombreux autres langages modernes (Java, Swift), vous ne pouvez pas utiliser une énumération comme condition dans une instruction if, ou comme argument pour! (ne pas). Merde, je viens d'appeler Java un "langage plus moderne" :-(


2 commentaires

Je ne vois toujours pas ce que l'inversion de la logique booléenne a à voir avec le fait d'être bogué, mais +1 quand même pour ce que j'ai déjà dit dans mon commentaire sous la question.


Pouvez-vous me dire ce qui ne va pas avec la première ligne?



3
votes

Vous avez plusieurs options, si vous n'avez besoin que d'un code de réussite et de plusieurs codes d'erreur.
Aucun n'est aussi naturel / idiomatique et donc agréable que si les choses étaient l'inverse:

  1. Utilisez un espace auxiliaire pour des informations supplémentaires:

    1. Utilisez errno pour Informations supplémentaires sur le transport pour les personnes intéressées.
      La bibliothèque standard fait cela de manière extensive.

    2. Stockez l'erreur spécifique dans l'objet utilisé. fgetc () le fait avec feof () et ferror().

    3. Définissez votre propre réserve (locale du thread) pour des informations supplémentaires sur les erreurs.

    4. Utilisez un rappel ou un pointeur de sortie supplémentaire.

    5. Renvoyez une struct avec tous les membres dont vous avez besoin.

  2. Utilisez une logique inversée, ce qui signifie que seul faux est le succès.
    Cela rappelle l'utilisation d'une fonction de comparaison générale comme strcmp () pour vérifier l'égalité.

  3. Dédiez une partie de la plage de valeurs de retour aux erreurs:

    1. Utilisez une erreur négative. La bonne chose est que vous avez tous les non-négatifs pour réussir.
      COM le fait largement avec leur HRESULT . Comme beaucoup d'appels système Linux.

    2. Les nombres à virgule flottante ont généralement de nombreuses valeurs NaN. On pourrait y mettre des détails, et certaines architectures garantissaient même de propager celle avec le plus petit code. Malheureusement, cela a été rarement utilisé, a un léger coût et n'a donc pas été suivi pour les nouvelles instructions.

    Il existe d'autres exemples moins pratiques.

Mon conseil est de le stocker dans l'objet manipulé si vous le pouvez, suivi de errno , puis de la logique inversée et enfin des conventions COM.


0 commentaires