What is a good general approach for determining return values ​​in C?

My program is written in C for Linux and has many functions with different patterns for return values:

1) one or two returns n on success and -1 on error.
2) some return 0 on success and -1 on failure.
3) some return 1 on success and 0 on failure (I usually reject using the boolean type).
4) pointers return 0 on failure (I usually reject using NULL ).

My confusion arises from the first three functions that the pointers return, always return 0 on failure, this is easy.

The first option usually includes functions that return a length that can only be positive.

The second option is usually associated with command line processing functions, but I'm not sure if it has the correctness, maybe higher values ​​will be EXIT_SUCCESS and EXIT_FAILURE?

The third option is for functions that are convenient and natural to call in conditions, and I usually emulate a Boolean type here, using int values ​​1 and 0.

Despite everything that seems reasonably reasonable, I still find areas where it is not so clear or obvious which style to use when I create a function, or which style to use when I want to use it.

So, how can I add clarity to my approach when choosing return types here?

+7
c function linux return-value
source share
8 answers

So, how can I add clarity to my approach when choosing return types here?

Choose one template for each type of return and stick to it, or you are going crazy. Model your template on conventions that have long been installed for the platform:

  • If you make a lot of system calls, then any integer return function should return -1 on failure.

  • If you are not making system calls, you can follow the convention of the C management structures that nonzero means success and zero means failure. (I don't know why you don't like bool .)

  • If the function returns a pointer, the failure must be indicated by returning NULL .

  • If the function returns a floating point number, a failure should be indicated by returning NaN.

  • If a function returns the full range of signed and unsigned integers, you probably shouldn't be successful or unsuccessful with encoding in the return value.

Testing return values ​​are programmers with programming. If a failure rarely occurs and you can write a central handler, consider using an exception macro that might indicate errors using longjmp .

+4
source share

Why don't you use the method used by the standard C library? Oh wait ...

+4
source share

Not a real answer to your question, but some random comments may seem interesting:

  • it is usually obvious when to use case (1), but it gets ugly when unsigned types are involved - return (size_t)-1 still works, but it's not pretty

  • if you use C99, there is nothing wrong with using _Bool ; imo, it is much cleaner than just using int

  • I use return NULL instead of return 0 in the contexts of the pointer (peronal preference), but I rarely check it, because I find it more natural to consider the handler as logical; the general case would look like this:

     struct foo *foo = create_foo(); if(!foo) /* handle error */; 
  • I try to avoid the case (2); using EXIT_SUCCESS and EXIT_FAILURE may be feasible, but this approach only makes sense if there are more than two possible results, and you still have to use enum

  • for more complex programs, it may make sense to implement your own error handling scheme; There are some pretty advanced implementations using setjmp() / longjmp() , but I prefer something errno -like with different variables for different types of errors.

+3
source share

One condition, I can think about where your above methodology may be unsuccessful, is a function that can return any value, including -1, like a function to add two signed numbers.

In this case, testing at -1 would probably be a bad idea.

If something fails, I would rather set the global error status flag provided by the C standard in the form of errno and use this to handle the error.
Although, the standard C ++ library provides exceptions that remove a lot of difficulties for error handling.

+2
source share

For can not be deterministic. Yes / no responses using a more specific return type (bool) can help maintain consistency. Further, for higher-level interfaces, you might consider returning or updating a specific message structure / summary.

My preference 0, to always be successful, is based on the following ideas:

  • Zero allows you to use the base class to organize failures with negative and positive values, such as complete failure and conditional success. I do not recommend this at all, as it tends to be too small to be useful and can lead to dangerous behavioral assumptions.

  • When success is zero, you can make a bunch of orthogonal calls and check the success of the group in one condition later, simply by comparing the group return code.

    rc = 0; rc + = func1 (); rc + = func2 (); rc + = func3 (); if (rc == 0) success!

  • Most importantly, my experience seems to be a consistent indicator of success when working with standard libraries and third-party systems.

+2
source share

So, how can I add clarity to my approach when choosing return types here?

Just the fact that you think about it is a long way. If you come up with one or two rules - or even more if they make sense (you may need more than one rule - as you mentioned, you can handle returned pointers differently than the others). I think you will be better than many stores.

Personally, I would like 0 to return to signal failure and not be zero to indicate success, but I have no strong need for it. I can understand a philosophy that could reverse this meaning so that you can return various reasons for rejection.

The most important thing is to follow the guidelines. It’s even better to have guidelines with documented justification (I believe that with justifications, people are more likely to follow these recommendations). As I said, only what you think about these things puts you ahead of many others.

0
source share

This is a matter of preference, but I noticed that this is inconsistency. Consider this using the pre C99 compiler

 #define SUCCESS 1
 #define ERROR 0

then any function that returns an int returns either one or the other in order to minimize confusion and adhere to it religiously. Again, depending on which, and given the development team, stick to their standards.

In pre C99 compilers, an int value is zero, and a value greater than zero is true. It depends on which standard your compiler is, if it is C99, use the stdbool _Bool type.

The great advantage of C is that you can use your personal style, but where teamwork is required, adhere to the standard command, which is laid out and follows it religiously, even after you leave this work, another programmer will thank you .

And keep the sequence.

Hope this helps, Regards, Tom.

0
source share

Most of the C standard library uses a strategy only to return true (or 1) to success and false (or 0) if it crashes and store the result in the passed place. More specific error codes than "failed" are stored in a special errno variable.

Something like this int add(int* result, int a, int b) that stores the result + b in * and returns 1 (or returns 0 and sets errno to a suitable value if, for example, a + b is greater than maxint) .

0
source share

All Articles