2

I was currently reading this question which shows problems about using void ** as a parameter to return a pointer from a function.

My code mostly has status codes as return values, and now I am looking for alternative ways to return these pointers AND a status code. So I currently see a couple of options, but none of them really make me happy. Probably I am overthinking a little.

// Output status through return value and the pointer through parameter 
// - seems to be problematic because it requires casting to void **, which is invalid
int myfunc(void **output);

// Output status through return value, pointer through struct 
// - seems to add unnecessary complexity to the interface
struct some_output { void *value };
int myfunc(struct some_output *output);

// Output pointer through return value, status through parameter 
// - breaks consistency with other interfaces which always return the status code
void *myfunc(int *status);

Now I am wondering whether there are other, alternative, elegant ways to return pointers and status codes from a function which I did not think about that don't have "drawbacks"?

Julius
  • 1,155
  • 9
  • 19
  • 4
    Why don't you return a struct? – Mat Nov 27 '19 at 12:27
  • 2
    You are allowed to return a struct, but I think most C programmers won't like this, so your first solution is the one with least surprise IMHO. – Vroomfondel Nov 27 '19 at 12:28
  • @Mat Thanks! That's another option... however, it still somehow breaks consistency with the rest of the interface which always return the status code :-( – Julius Nov 27 '19 at 12:28
  • @Vroomfondel I like the first one most as well, but it seems to be against the standard :-( – Julius Nov 27 '19 at 12:29
  • Which standard? The C standard absolutely allows this as long as you touch the object which `*output` points to with the correct stored .type – Vroomfondel Nov 27 '19 at 12:31
  • Hm, true. The problem only arises when casting, e.g. `int *myval; myfunc((void **)&myval);` – Julius Nov 27 '19 at 12:32
  • If you want to return 2 things, return 2 things in a `struct` with 2 members. If you you don't like returning a `struct`, don't return 2 things. – chux - Reinstate Monica Nov 27 '19 at 12:54
  • 1
    There shouldn't be any reason to use a `void**`. Name one. If your function uses dynamic allocation internally, then return the type allocated through `type**`. – Lundin Nov 27 '19 at 12:56
  • How many different status codes are you returning? If you're just returning success/failure, just return the pointer if you succeed or `NULL` otherwise. That's good enough for `malloc()`, and `fopen()`, and .... – Andrew Henle Nov 27 '19 at 12:56
  • @AndrewHenle I'm afraid it's more than just a sucess/fail – Julius Nov 27 '19 at 13:02
  • @Lundin let's imagine a "generic" function like calloc but with more status codes than just success/failure? – Julius Nov 27 '19 at 13:26
  • @Julius Such a function should use a character type like `uint8_t`. It is not really possible to implement malloc safely in standard C because of pointer aliasing. – Lundin Nov 27 '19 at 13:32
  • @Lundin interesting. even if I was allocating structs with it? could you ellaborate your thoughts why uint8_t is a good fit? – Julius Nov 27 '19 at 13:37
  • [What is the strict aliasing rule?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule). – Lundin Nov 27 '19 at 13:38
  • 1
    "Probably I am overthinking a little." No. Its the other way around. Post lacks definition of `status` other than "it's more than just a sucess/fail". It could be a 3 value `int`, a string, a struct, ... An _elegant_ solution needs far more detail as to what you are trying to do. As is - this is under-defined. We could spend a lot a comments (15 so far) teasing out that info. Instead, consider deleting the post and later presenting a more clear explanation of the coding goal. – chux - Reinstate Monica Nov 27 '19 at 13:38
  • @chux-ReinstateMonica I agree, these examples are far too artificial to do anything meaningful with. – Lundin Nov 27 '19 at 13:52
  • This is as opinion based as it gets. – n. m. could be an AI Nov 27 '19 at 14:04
  • Julius This a **good** question, but not for SO as is - too general. How to handle passing data and error info gets to the core of a language. C offers may ways in addition to this post. It is just that we have no closing criteria, no way to cross rate answers given its board scope. Try focusing on a specific case first. – chux - Reinstate Monica Nov 27 '19 at 14:23

2 Answers2

1

With 'C', when functions are limited to returning a single value, there is no one perfect method. There are few patterns that are commonly being used, and are followed by various available API. Consider sticking with one of the proven, less then perfect, methods:

  • int status = function(struct Result *output, input) ;

    • Work for simple cases, with small number of success/failures.
    • Common to use 0 and positive for success, negative for errors.
  • int result = function(*output, input) ; with extended error code.

    • Used with used for many Linux system calls/APIs, extra error detail in 'errno'.
    • Common to use 0 and positive for success, negative for errors.
    • Challenge with MT systems, because of single error code. In many cases, error information actually available as functions that wraps thread local result.
  • bool success = function(*output, input) ; with callback error

    • Making it easy to pass success/failure.
    • Error information passed to user defined error callback.
    • Implemented in many GUI callbacks (e.g, X11), already using callbacks.
  • struct result *res = function(input, struct errro **error)

    • Used in Glib or other libraries which handle complex data types (not just arrays)
    • Usually, each structure will have corresponding free* functions.
    • Error address (if passed) will capture error data.
    • Error will result in res = NULL, and error being set.
    • Closer in spirit to try/catch.

When introducing generic calls, the common theme that I observed is usually to have the output and the error objects at the same location in the argument list (not necessary togethe!). In many cases the output is placed as the first, and the error/exception is placed at the end.

Worth looking into Error handling in C code

dash-o
  • 13,723
  • 1
  • 10
  • 37
  • 1
    Anything revolving around "errno" or "get last error" crap is to be regarded as obsolete IMO. We've had multithreaded applications for some 30 years and regardless of that, polling some global variable isn't good API design, never was. The canonical form is rather `err_t status = function(struct Result *output, input) ;` where "err_t" is some custom enum type. Using `int` or bool is often too blunt. – Lundin Nov 27 '19 at 15:16
0

I agree with @Mat use a:

typedef Gret struct generic_ret;
struct generic_ret {
    int Status;
    union {
     void *p;
     DialPlan *d;
    };
}
...
Phone = GetPhone(...);
if (Phone.Status == 0) {
     Dial(Phone.d);
     ...
}

Keep that up and you will end up programming in Go... The other classic approach to status + pointer is to reserve more pointer values than 0:

extern DialPlan *DP_NoService, *DP_BadNumber, *DP_Broke, ...;
extern bool IsDialError(DialPlan *p);
....
static DialPlan *DP_FirstError = 0,
                *DP_NoService = (void *)1,
                *DP_BadNumber = (void *)2,
                *DP_LastError = (void *)10;
bool IsDialError(DialPlan *p) {
     return (p > DP_FirstError && p < DP_LastError);
}

Or, if you prefer the look of safety:

static DialPlan Errors[10];
static DialPlan *DP_NoService = Errors + 0,
                *DP_BadNumber = Errors + 1,
                ...
                *DP_LastError = Errors + 9;
mevets
  • 10,070
  • 1
  • 21
  • 33
  • Besides passing structs by value being bad practice, `void*` is not required to have the same representation as an object pointer. As for the obscure pointer tricks, I'd hardly call that "classic". Classic quality programming is rather to separate data from error codes, and don't touch the data at all in case of errors. Your integer to pointer example is also wildly undefined behavior: you can't do pointer arithmetic on null pointers, nor on pointers pointing at different objects. If you insist on doing icky things like these, at least convert to an integer type before you do the comparisons. – Lundin Nov 27 '19 at 13:50
  • 1
    Classic, as in *in active use for decades*. Passing, and returning structs stopped being controversial in C in the 1980s. – mevets Nov 27 '19 at 13:52
  • Yeah C programmers have been invoking undefined behavior for decades. What's your point? As for passing structs, plenty of embedded system ABIs produce very ineffective code for that in the year 2019. Not every computer is a PC. – Lundin Nov 27 '19 at 13:58
  • 1
    Not undefined; if you ride a bus, take a train, ride a plane or drive in a car, you will be running in safety certified kernels that use both of the above techniques. – mevets Nov 27 '19 at 13:59
  • Sorry but there's just no way in h*** that this code will pass a simple MISRA checker, let alone ASIL certification. – Lundin Nov 27 '19 at 14:08
  • Are you saying that this "hack" is MISRA compliant? Sorry, but that's a bit scary. – Bob__ Nov 27 '19 at 14:08
  • 1
    1) `(void *)1` can fail due to "the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation." C11 §6.3.2.3 5. Instead code could use `static DialPlan DP_FirstError;` (no `*`). ( I now see rectified in later part of answer) 2) Unfortunately `(p > DP_FirstError && p < DP_LastError)` is UB when not true. – chux - Reinstate Monica Nov 27 '19 at 14:16