Home > Software engineering >  Returning error codes from a C function which returns object pointer?
Returning error codes from a C function which returns object pointer?

Time:11-08

This is the signature of my object create function:

struct object *object_create();

If the object is successfully created it it returns pointer to the object, and 0 otherwise.

How would I return some error codes from the same function? I need about ten codes.

I suppose mixing error codes and object pointer is not the way to go?

CodePudding user response:

You have multiple options:

  1. Set errno.
  2. Change the function signature to either of these:
    1. int object_create(struct object **ret);
    2. struct object *object_create(int *err);
  3. Declare and use your own errno equivalent (care needs to be taken to make this work safely with threads, i.e. you probably want a thread-local variable).
  4. Create a structure that can hold your object as well as an error code; either struct object itself, or some wrapper. This option isn’t as common in C since it works best with generic types (e.g. C , where you could create a generic Result<T, Err> type).

CodePudding user response:

There are pretty much only two acceptable options:

  1. struct object *object_create (myerr_t* result, /* ... */);
    Return the struct and pass the function result as parameter.

  2. myerr_t object_create (struct object** obj, /* ... */);
    Return the function result and pass a pointer to the struct by parameter.

Pros of 1:

  • It has the advantage that you can assign variables directly on the caller side:
    struct object* obj = object_create(/* ... */);
  • Pointer-to-pointer interfaces add a slight bit of complexity which can be avoided this way.

Pros of 2:

  • Reserving the return value of a function for an error code is an industry "de facto" standard way of designing APIs/libs. It is common to to use the same result type for the whole lib and have every function return it.
  • You have the option not to modify the struct in case of errors, leaving the caller's variable intact. Mainly matters in case of "realloc-like" interfaces.

I would not advise anyone to use errno because using a global error result variable is obscure, error prone, severely restricted and (pre-C11) not thread-safe. Also it might conflict with skunky, legacy error handling by various standard/POSIX lib functions.

I would not advise to use any wrapper hacks either, such as wrapping the returned struct in a larger one or over-allocating an error code at the end. This just adds complexity and potential for bugs.

An error handler should be simple, not introduce potential errors of its own due to added complexity! Anything else but a plain enum result code is questionable practice.

CodePudding user response:

Not sure it is advisable (in fact, I am pretty sure it is not), but I have seen people doing things like (ok, I admit, I am the one doing it. I am not always doing advisable things).

#include <stdio.h>
#include <stdlib.h>

# define ONE (struct object *)1

// Example structure
struct object {
    char *name;
};

struct object *object_create(){
    struct object *ret=(struct object *)malloc(sizeof(struct object));
    if(!ret) return NULL;
    ret->name = malloc(80);
    if(!ret->name){
        free(ret);
        return ONE;
    }
    return ret;
}

int main(){
    struct object *myobj=object_create();
    if(myobj==NULL){
        fprintf(stderr, "Cannot create object\n");
        return 1;
    }
    if(myobj==ONE){
        fprintf(stderr, "Cannot allocate memory for name\n");
        return 2;
    }
    // start playing with myobj
    sprintf(myobj->name, "Hello");
}

Argument is because of alignment, and because of pragmatism knowledge of how segments works, malloc could never return 1.

You could even double ensure that, my mallocing memory just for your sort of enum.

void initEnum(struct object *code1, struct object *code2, struct object *code3){
    struct object *ret=(struct object *)malloc(3);
    code1=ret;
    code2=(struct object *)(1 (char *)ret);
    code3=(struct object *)(2 (char *)ret);
}

And then, use code1, code2, code3 as special meaning values for your object. (Note that there is no memory allocated for those object. Just a few bytes by malloc(3), that makes sure that no object can share those addresses). So, it is a little bit like NULL: an address that points to nowhere, and whose only purpose is to be known to points to no legit object, and therefore to be usable for this king a special meaning (NULL is just (struct object*)0).

  •  Tags:  
  • c
  • Related