Reputation: 5611
I have a basic question regarding proper program structure in c.
Let's say my main function calls several other functions to configure a specific piece of hardware (like an ethernet card), and that each of those functions calls more basic functions to deal with more specific configuration on that ethernet card.
The the lower level functions all have return values that specify whether they were completed successfully. Is it most proper to continue this paradigm all the way back to main?
For example, if one of my lower level functions fails, should I be doing the following?:
check its return value --> return to calling function --> check its return value --> return to calling function ...
all the way back to main?
Or does it make more sense to have main just assume everything is functioning normally (not considering return values of the functions it calls) and returning 0; then calling exit(EXIT_FAILURE) from the lower level functions?
I greatly appreciate the help.
Upvotes: 3
Views: 2252
Reputation: 47954
There are multiple separate issues here.
As for whether you should propagate errors up or just call exit(EXIT_FAILURE)
at the point you detect a problem, I'd generally recommend propagating the errors up. If the low-level code propagates the error up, it leaves open the possibility that the higher-level code could try to handle the error (e.g., report it to the user, retry, try a different configuration, etc.). If the low-level code bails out, the higher level code doesn't have a chance to deal with it. (The flipside is that the higher level code has to be prepared to do something appropriate and not just carry on assuming there were no errors.)
As for what those errors values should be, I'd generally recommend defining your own return status types (e.g., as an enum). EXIT_FAILURE and EXIT_SUCCESS are intended to be inputs to exit
, and you should treat them as opaque values. For example, don't assume that EXIT_SUCCESS is 0.
As for whether you should return from main
or call exit
, that's up to you. To be super-hyper-portable, I recommend calling exit
with EXIT_SUCCESS or EXIT_FAILURE as appropriate. However, it's common to simply return 0
for success or return nonzero
for failure. The latter practice doesn't work well on some obscure, mostly dead operating systems with brain-dead compilers. In any event, I would never return EXIT_SUCCESS
from main
.
Upvotes: 1
Reputation: 6003
For academia level applications, I don't think it matters. . However, for production (enterprise) applications, my (30-years) experience is:
1) Never call exit() unless it's a last resort, where the user needs to
contact the application developer to resolve an unanticipated condition.
2) In order to ensure maintainability, functions should not contain more
than one return statement.
All of the functions I produce (for use in an enterprise production environment) have a similar flow:
int SomeEnterpriseQualityFunction(
SOME_TYPE1_T I__someValueBeingPassedIntoTheFunction,
...
SOME_TYPE2_T *IO_someValueBeingModifiedByTheFunction,
...
SOME_TYPE3_T **_O_someValueBeingReturnedByTheFunction
)
{
int rCode=0;
int someFileHandle=(-1);
void *someMemory = NULL;
SOME_TYPE3_T *someValueBeingReturnedByTheFunction=NULL;
/* Validate user input parameters */
if( /*I__someValueBeingPassedIntoTheFunction is out of range */)
{
rCode=ERANGE;
goto CLEANUP;
}
if( NULL == IO_someValueBeingModifiedByTheFunction )
{
rCode=EINVAL;
goto CLEANUP;
}
/* Acquire resources */
someFileHandle=open(/*someFileName, mode, etc.*/);
if((-1) == someFileHandle)
{
rCode=errno;
goto CLEANUP;
}
someMemory=malloc(/* bytesToMalloc */);
if(NULL == someMemory)
{
rCode=ENOMEM;
goto CLEANUP;
}
/* Actual work done here. */
....
if(/* Successfully finished work at this point... */)
goto RESULT;
...
...
/* Return results to caller here. */
RESULT:
if(_O_someValueBeingReturnedByTheFunction);
{
*_O_someValueBeingReturnedByTheFunction = someValueBeingReturnedByTheFunction;
someValueBeingReturnedByTheFunction = NULL;
}
/* Release acquired resources. */
CLEANUP:
if(someValueBeingReturnedByTheFunction)
{
int rc= /* Clean-up someValueBeingReturnedByTheFunction related resources,
since the caller didn't need it. */
if(0==rCode)
rCode=rc;
}
if(someMemory)
free(someMemory);
if((-1) != someFileHandle)
{
if((-1) == close(someFileHandle) && 0 == rCode)
rCode=rc;
}
return(rCode);
}
And, not matter what your instructors might say about goto, they are far from -evil- when used in this way for error handling.
Upvotes: 4
Reputation: 20383
In my opinion, calling exit( EXIT_FAILURE )
right from the failing function is okay. If some cleanup is necessary, then I would use atexit()
From http://linux.die.net/man/3/exit
#include <stdlib.h>
void exit(int status);
The exit() function causes normal process termination ...
All functions registered with atexit(3) and on_exit(3) are called,
in the reverse order of their registration.
Upvotes: 0