3

I'm writing a wrapper for realloc. For return values, I use this:

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

This is used in a function like this:

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

This is of course not the complete code, but it is enough to demonstrate what I want. So far so good. I can check the return value like this:

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

or if I want to be more precise:

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

However, I want this to be compatible with the usual way of checking for error. I want to be able to use:

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

and

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

The problem here is that 0 is false, and EVERYTHING else is true. What I want to do seems impossible, but there might be some way around it that I have missed. The only solution I have found so far is to give the function an extra argument with a pointer to a place where I can store the error code, but I want to avoid that if possible.

TL;DR

How to make a function <type> foo() able to return different error messages depending on what went wrong while still keeping the possibility to check for error in a "traditional" way, like

if (!foo()) exit(EXIT_FAILURE);
klutt
  • 30,332
  • 17
  • 55
  • 95
  • 4
    I prefer `if(salloc(a,b) != SALLOC_OK)`. It's more explicit, and makes your intent clearer. There's no way to make zero mean more than one thing without making things more complex and less clear than this. – Robert Harvey Jan 26 '19 at 21:00
  • Aside: Note that when `realloc()` returns `NULL`, that itself does not mean `realloc()` failed. `realloc(ptr, 0)` is a potential valid (non-out-of-memory) response. I can see why you want a wrapper for `realloc()`. – chux - Reinstate Monica Jan 26 '19 at 21:46
  • "possible to return different “false” in C?" Sure - return 0.0 or -0.0, both are false and can be [discerned](https://stackoverflow.com/q/25332133/2410359) from each other when `-0.0` is supported - but I do not think a FP answer is a good approach here. – chux - Reinstate Monica Jan 26 '19 at 21:53
  • @chux Nice one there, but I want to be able to differentiate between more than two different errors. – klutt Jan 26 '19 at 21:55
  • @Broman Grumble, grumble [next 4 will be wanted](https://en.wikipedia.org/wiki/Mission_creep), grumble, grumble, grumble. – chux - Reinstate Monica Jan 26 '19 at 22:01
  • @chux You're completely correct. Sorry about that. I guess I'll excuse myself with that I did not expect someone to find such a loophole. :D – klutt Jan 26 '19 at 22:05
  • @Broman Interestingly, a system can support multiple _null pointers_, all equate to `0`, `NULL` and each other (false), yet with different bit patterns - so can be differentiated on other ways. And with ancient non-two's complement machines, we have -0, +0 integers (both false) and differentiable in other ways. Yet I suspect the cleanest approach here is to return an `int`-like _error-code_. 0: no error, else: error. Best explored with this [good answer](https://stackoverflow.com/a/54382869/2410359). – chux - Reinstate Monica Jan 26 '19 at 22:15
  • What is wrong with `switch (salloc(a,b)) case SALLOC_OK: ... case SALLOC_OVERFLOW: ... case SALLOC_ALLOCFAIL: ....`? – chux - Reinstate Monica Jan 26 '19 at 23:26

3 Answers3

3

If you write

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

that's just plain wrong. Serious bug. But if you write

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

then I as the reader of your code have no idea whether this statement is correct or not, which forces me to read to documentation. So the correct way to do this is:

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

It's clean, it tells the reader exactly what's going on, it gives a chance to set a breakpoint where you examine the return code. Win all over. Don't be afraid of some extra keystrokes. Make your code readable. And if you are told about a "usual way to check for errors" that makes your code hard to read, then don't use the "usual way to check for errors".

Note that in many more modern languages (Java, Swift) you cannot use an enum as the condition in an if-statement, or as the argument for ! (not). Damned, I just called Java a "more modern language" :-(

gnasher729
  • 51,477
  • 5
  • 75
  • 98
3

You have multiple options, if you only need one success-code and multiple error-codes.
None is as natural / idiomatic and thus nice as if things were the other way around:

  1. Use some auxiliary space for additional Information:

    1. Use errno to transport supplemental info for those interested.
      The standard-library does that extensively.

    2. Store the specific error in the used object. fgetc() does so with feof() and ferror().

    3. Define your own (thread-local) stash for supplemental error-info.

    4. Use a callback or additional output-pointer.

    5. Return a struct with all the members you need.

  2. Use inverted logic, meaning only false is success.
    That reminds of using a general comparison-function like strcmp() to check equality.

  3. Dedicate some of the return-values range to errors:

    1. Use negative means error. The nice thing is you have all non-negatives for success.
      COM does that extensively with their HRESULT. As do many Linux syscalls.

    2. Floating-point numbers generally have many NaN values. One could put specifics in there, and some architectures even guaranteed to propagate the one with the smallest code. Unfortunately, that was rarely used, has a slight cost, and was thus not followed for new instructions.

    There are additional less convenient examples.

My advice is to store it in the manipulated object if you can, followed by errno, then negated logic and finally COM conventions.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
1

It usually done the right opposite way.

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

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

It is very simple logic - if function returns non zero - it means somehing wrong.

Real life examples: STM Libraries: /* Following USB Device status */

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

Where NRF_ERROR_BASE_NUM is usually 0

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Is it really usually done the opposite way? At least it isn't for the memory allocation functions. A NULL pointer is returned on failure. – klutt Jan 26 '19 at 21:04
  • @Broman his implenemtation does not return the pointer only some enum as i understand. So it is not the typical memory allocation function – 0___________ Jan 26 '19 at 21:09