2.5 Function Return Values and Errors

Team-FLY

Error handling is a key issue in writing reliable systems programs. When you are writing a function, think in terms of that function being called millions of times by the same application. How do you want the function to behave? In general, functions should never exit on their own, but rather should always indicate an error to the calling program. This strategy gives the caller an opportunity to recover or to shut down gracefully.

Functions should also not make unexpected changes to the process state that persist beyond the return from the function. For example, if a function blocks signals, it should restore the signal mask to its previous value before returning.

Finally, the function should release all the hidden resources that it uses during its execution. Suppose a function allocates a temporary buffer by calling malloc and does not free it before returning. One call to this function may not cause a problem, but hundreds or thousands of successive calls may cause the process memory usage to exceed its limits. Usually, a function that allocates memory should either free the memory or make a pointer available to the calling program. Otherwise, a long-running program may have a memory leak ; that is, memory "leaks" out of the system and is not available until the process terminates.

You should also be aware that the failure of a library function usually does not cause your program to stop executing. Instead, the program continues, possibly using inconsistent or invalid data. You must examine the return value of every library function that can return an error that affects the running of your program, even if you think the chance of such an error occurring is remote .

Your own functions should also engage in careful error handling and communication. Standard approaches to handling errors in UNIX programs include the following.

  • Print out an error message and exit the program ( only in main ).

  • Return “1 or NULL , and set an error indicator such as errno .

  • Return an error code.

In general, functions should never exit on their own but should always report an error to the calling program. Error messages within a function may be useful during the debugging phase but generally should not appear in the final version. A good way to handle debugging is to enclose debugging print statements in a conditional compilation block so that you can reactivate them if necessary.

Example 2.10

The following code segment shows an example of how to use conditional compilation for error messages in functions.

 #define DEBUG    /* comment this line out for no error messages */     int myfun(int x) {        x++;     #ifdef DEBUG        fprintf(stderr, "The current value of x is %d\n", x);     #endif } 

If you comment the #define line out, the fprintf statement is not compiled and myfun does no printing. Alternatively, you can leave the #define out of the code completely and define DEBUG on the compiler line as follows .

 cc -DDEBUG ... 

Most library functions provide good models for implementing functions. Here are guidelines to follow.

  1. Make use of return values to communicate information and to make error trapping easy for the calling program.

  2. Do not exit from functions. Instead, return an error value to allow the calling program flexibility in handling the error.

  3. Make functions general but usable. (Sometimes these are conflicting goals.)

  4. Do not make unnecessary assumptions about sizes of buffers. (This is often hard to implement.)

  5. When it is necessary to use limits, use standard system-defined limits rather than arbitrary constants.

  6. Do not reinvent the wheel ”use standard library functions when possible.

  7. Do not modify input parameter values unless it makes sense to do so.

  8. Do not use static variables or dynamic memory allocation if automatic allocation will do just as well.

  9. Analyze all the calls to the malloc family to make sure the program frees the memory that was allocated.

  10. Consider whether a function is ever called recursively or from a signal handler or from a thread. Functions with variables of static storage class may not behave in the desired way. (The error number can cause a big problem here.)

  11. Analyze the consequences of interruptions by signals.

  12. Carefully consider how the entire program terminates.

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net