klutt
klutt

Reputation: 31296

Is it possible to return different "false" in C?

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);

Upvotes: 3

Views: 163

Answers (3)

Deduplicator
Deduplicator

Reputation: 45654

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.

Upvotes: 3

gnasher729
gnasher729

Reputation: 52538

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" :-(

Upvotes: 3

0___________
0___________

Reputation: 67476

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

Upvotes: 1

Related Questions