📜 ⬆️ ⬇️

Error Handling in C



Introduction


Errors, alas, are unavoidable, so their processing takes a very important place in programming. And if algorithmic errors can be identified and corrected during the writing and testing of the program, then run-time errors cannot be avoided in principle. Today we look at the functions of the standard library ( C Standard Library ) and POSIX , used in error handling.

Errno variable and error codes


<errno.h>


errno is a variable storing the integer code of the last error. Each stream has its own local version of errno , which is the reason for its security in a multithreaded environment. Usually, errno is implemented as a macro that expands into a function call that returns a pointer to an integer buffer. When the program starts, the errno value is zero.

All error codes have positive values, and can be used in preprocessor directives #if . For convenience and portability, the <errno.h> header file defines macros that correspond to error codes.
')
The ISO C standard defines the following codes:


Other error codes (several dozen) and their descriptions are defined in the POSIX standard. In addition, the specifications of standard functions usually indicate the error codes used by them and their descriptions.

A simple script prints error codes, their symbolic names and descriptions to the console:

 #!/usr/bin/perl use strict; use warnings; use Errno; foreach my $err (sort keys (%!)) { $! = eval "Errno::$err"; printf "%20s %4d %s\n", $err, $! + 0, $! } 

If the function call fails, it sets the errno variable to a nonzero value. If the call is successful, the function usually does not check and does not change the errno variable. Therefore, before calling a function, it must be set to 0 .

Example:

 /* convert from UTF16 to UTF8 */ errno = 0; n_ret = iconv(icd, (char **) &p_src, &n_src, &p_dst, &n_dst); if (n_ret == (size_t) -1) { VJ_PERROR(); if (errno == E2BIG) fprintf(stderr, " Error : input conversion stopped due to lack of space in the output buffer\n"); else if (errno == EILSEQ) fprintf(stderr, " Error : input conversion stopped due to an input byte that does not belong to the input codeset\n"); else if (errno == EINVAL) fprintf(stderr, " Error : input conversion stopped due to an incomplete character or shift sequence at the end of the input buffer\n"); /* clean the memory */ free(p_out_buf); errno = 0; n_ret = iconv_close(icd); if (n_ret == (size_t) -1) VJ_PERROR(); return (size_t) -1; } 

As you can see, the error descriptions in the iconv() function specification are more informative than in <errno.h> .

Errno functions


Having received the error code, I want to immediately get a description of it. Fortunately, ISO C offers a whole host of useful features.

<stdio.h>


void perror(const char *s);

Prints stder with the contents of s , followed by a colon, a space, and an error message. Then it prints the newline character '\n' .

Example:

 /* // main.c // perror example // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <errno.h> int main(int argc, const char * argv[]) { // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(file_name, "rb"); if (file) { // Do something useful. fclose(file); } else { perror("fopen() "); } return EXIT_SUCCESS; } 

<string.h>


char * strerror (int errnum);
Returns a string describing the error errnum . The language of the message depends on the locale (German, Hebrew and even Japanese), but usually only English is supported.

 /* // main.c // strerror example // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(int argc, const char * argv[]) { // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(file_name, "rb"); // Save error number. errno_t error_num = errno; if (file) { // Do something useful. fclose(file); } else { char *errorbuf = strerror(error_num); fprintf(stderr, "Error message : %s\n", errorbuf); } return EXIT_SUCCESS; } 

strerror() not a safe function. First, the string returned by it is not constant. However, it can be stored in static or in dynamic memory, depending on the implementation. In the first case, changing it will result in a run-time error. Secondly, if you decide to save a pointer to a string, and then call a function with a new code, all previous pointers will point to a new row, because it uses one buffer for all rows. Third, its behavior in a multithreaded environment is not defined in the standard. However, in QNX, it is declared as thread safe.

Therefore, in the new ISO standard C11 , two very useful functions were proposed.

size_t strerrorlen_s(errno_t errnum);

Returns the length of the string describing the error errnum .

errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);

Copies the string with the errnum error errnum to the buf buffer of length buflen .

Example:

 /* // main.c // strerror_s example // // Created by Ariel Feinerman on 23/02/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(int argc, const char * argv[]) { // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(file_name, "rb"); // Save error number. errno_t error_num = errno; if (file) { // Do something useful. fclose(file); } else { #ifdef __STDC_LIB_EXT1__ size_t error_len = strerrorlen_s(errno) + 1; char error_buf[error_len]; strerror_s(error_buf, error_len, errno); fprintf(stderr, "Error message : %s\n", error_buf); #endif } return EXIT_SUCCESS; } 

Functions are included in Annex K (Bounds-checking interfaces), which caused a lot of controversy . It is not required to be executed and is not fully implemented in any of the free libraries. Open Watcom C / C ++ (Windows), Slibc (GNU libc) and Safe C Library (POSIX), in the latter, unfortunately, these two functions are not implemented. However, they can be found in commercial development environments and real-time systems, Embarcadero RAD Studio , INtime RTOS , QNX .

The POSIX.1-2008 standard defines the following functions:

char *strerror_l(int errnum, locale_t locale);

Returns a string containing a localized description of the errnum error using locale . Safe in multithreaded environment. Not implemented on Mac OS X , FreeBSD , NetBSD , OpenBSD , Solaris, and other commercial UNIX. Implemented in Linux, MINIX 3 and Illumos (OpenSolaris).

Example:

 /* // main.c // strerror_l example – works on Linux, MINIX 3, Illumos // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <locale.h> int main(int argc, const char * argv[]) { locale_t locale = newlocale(LC_ALL_MASK, "fr_FR.UTF-8", (locale_t) 0); if (!locale) { fprintf(stderr, "Error: cannot create locale."); exit(EXIT_FAILURE); } // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(tmpnam(file_name, "rb"); // Save error number. errno_t error_num = errno; if (file) { // Do something useful. fclose(file); } else { char *error_buf = strerror_l(errno, locale); fprintf(stderr, "Error message : %s\n", error_buf); } freelocale(locale); return EXIT_SUCCESS; } 

Conclusion:

 Error message : Aucun fichier ou dossier de ce type 

int strerror_r(int errnum, char *buf, size_t buflen);

Copies the string with the errnum error errnum to the buf buffer of length buflen . If buflen less than the length of the string, the excess is trimmed. Safe in multithreaded environment. Implemented in all UNIX.

Example:

 /* // main.c // strerror_r POSIX example // // Created by Ariel Feinerman on 25/02/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MSG_LEN 1024 int main(int argc, const char * argv[]) { // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(file_name, "rb"); // Save error number. errno_t error_num = errno; if (file) { // Do something useful. fclose(file); } else { char error_buf[MSG_LEN]; errno_t error = strerror_r (error_num, error_buf, MSG_LEN); switch (error) { case EINVAL: fprintf (stderr, "strerror_r() failed: invalid error code, %d\n", error); break; case ERANGE: fprintf (stderr, "strerror_r() failed: buffer too small: %d\n", MSG_LEN); case 0: fprintf(stderr, "Error message : %s\n", error_buf); break; default: fprintf (stderr, "strerror_r() failed: unknown error, %d\n", error); break; } } return EXIT_SUCCESS; } 

Alas, no analogue of strerrorlen_s() in POSIX was determined, so the length of the string can only be determined experimentally. Usually 300 characters are enough for the eyes The GNU C Library implementation of strerror() uses a buffer with a length of 1024 characters. But you never know, and suddenly?

Example:

 /* // main.c // strerror_r safe POSIX example // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MSG_LEN 1024 #define MUL_FACTOR 2 int main(int argc, const char * argv[]) { // Generate unique filename. char *file_name = tmpnam((char[L_tmpnam]){0}); errno = 0; FILE *file = fopen(file_name, "rb"); // Save error number. errno_t error_num = errno; if (file) { // Do something useful. fclose(file); } else { errno_t error = 0; size_t error_len = MSG_LEN; do { char error_buf[error_len]; error = strerror_r (error_num, error_buf, error_len); switch (error) { case 0: fprintf(stderr, "File : %s\nLine : %d\nCurrent function : %s()\nFailed function : %s()\nError message : %s\n", __FILE__, __LINE__, __func__, "fopen", error_buf); break; case ERANGE: error_len *= MUL_FACTOR; break; case EINVAL: fprintf (stderr, "strerror_r() failed: invalid error code, %d\n", error_num); break; default: fprintf (stderr, "strerror_r() failed: unknown error, %d\n", error); break; } } while (error == ERANGE); } return EXIT_SUCCESS; } 

Conclusion:

 File : /Users/ariel/main.c Line : 47 Current function : main() Failed function : fopen() Error message : No such file or directory 

Macro assert ()


<assert.h>


void assert(expression)

A macro that checks the expression condition (its result must be a number) at run time. If the condition is not satisfied ( expression is zero), it prints the values __FILE__ , __LINE__ , __func__ and expression as strings to stderr , and then calls the abort() function.

 /* // main.c // assert example // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <math.h> int main(int argc, const char * argv[]) { double x = -1.0; assert(x >= 0.0); printf("sqrt(x) = %f\n", sqrt(x)); return EXIT_SUCCESS; } 

Conclusion:

 Assertion failed: (x >= 0.0), function main, file /Users/ariel/main.c, line 17. 

If the NDEBUG macro NDEBUG defined before including <assert.h> , then assert() expanded to ((void) 0) and does nothing. Used for debugging purposes.

Example:

 /* // main.c // assert_example // // Created by Ariel Feinerman on 23/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #NDEBUG #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <math.h> int main(int argc, const char * argv[]) { double x = -1.0; assert(x >= 0.0); printf("sqrt(x) = %f\n", sqrt(x)); return EXIT_SUCCESS; } 

Conclusion:

 sqrt(x) = nan 

Atexit () , exit (), and abort () functions


<stdlib.h>


int atexit(void (*func)(void));

Registers the functions called upon the normal completion of the program in the reverse order of their registration. You can register up to 32 functions.

_Noreturn void exit(int exit_code);

Causes normal program termination, returns the number exit_code on Wednesday. The ISO C standard defines only three possible values: 0 , EXIT_SUCCESS and EXIT_FAILURE . In this case, functions registered through atexit() are called, input-output streams are reset and closed, temporary files are deleted, after which control is transferred to the environment. The exit() function is called in main() when executing a return or reaching the end of a program.

The main advantage of exit() is that it allows you to terminate the program not only from main() , but also from any nested function. For example, if in a deeply nested function a certain condition is fulfilled (or not fulfilled), after which further program execution loses all meaning. Such a reception (early exit) is widely used when writing daemons, system utilities and parsers. In interactive programs with an infinite main loop, exit() can be used to exit the program by selecting the desired menu item.

Example:

 /* // main.c // exit example // // Created by Ariel Feinerman on 17/03/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <math.h> void third_2(void) { printf("third #2\n"); // Does not print. } void third_1(void) { printf("third #1\n"); // Does not print. } void second(double num) { printf("second : before exit()\n"); // Prints. if ((num < 1.0f) && (num > -1.0f)) { printf("asin(%.1f) = %.3f\n", num, asin(num)); exit(EXIT_SUCCESS); } else { fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]\n", num); exit(EXIT_FAILURE); } printf("second : after exit()\n"); // Does not print. } void first(double num) { printf("first : before second()\n") second(num); printf("first : after second()\n"); // Does not print. } int main(int argc, const char * argv[]) { atexit(third_1); // Register first handler. atexit(third_2); // Register second handler. first(-3.0f); return EXIT_SUCCESS; } 

Conclusion:

 first : before second() second : before exit() Error: -3.0 is beyond the range [-1.0; 1.0] third #2 third #1 

_Noreturn void abort(void);

Causes a program to crash if the signal has not been intercepted by a signal handler. Temporary files are not destroyed, closing threads is determined by the implementation. The most important difference between the abort () and exit(EXIT_FAILURE) calls is that the first one sends the SIGABRT signal to the program, it can be intercepted and the necessary actions can be taken before the program ends. A core dump file of the program is recorded, if enabled. When launched in the debugger, it intercepts the SIGABRT signal and stops the execution of the program, which is very convenient in debugging.

Example:

 /* // main.c // abort example // // Created by Ariel Feinerman on 17/02/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <math.h> void third_2(void) { printf("third #2\n"); // Does not print. } void third_1(void) { printf("third #1\n"); // Does not print. } void second(double num) { printf("second : before exit()\n"); // Prints. if ((num < 1.0f) && (num > -1.0f)) { printf("asin(%.1f) = %.3f\n", num, asin(num)); exit(EXIT_SUCCESS); } else { fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]\n", num); abort(); } printf("second : after exit()\n"); // Does not print. } void first(double num) { printf("first : before second()\n"); second(num); printf("first : after second()\n"); // Does not print. } int main(int argc, const char * argv[]) { atexit(third_1); // register first handler atexit(third_2); // register second handler first(-3.0f); return EXIT_SUCCESS; } 

Conclusion:

 first : before second() second : before exit() Error: -3.0 is beyond the range [-1.0; 1.0] Abort trap: 6 

Debugger output:

 $ lldb abort_example (lldb) target create "abort_example" Current executable set to 'abort_example' (x86_64). (lldb) run Process 22570 launched: '/Users/ariel/abort_example' (x86_64) first : before second() second : before exit() Error: -3.0 is beyond the range [-1.0; 1.0] Process 22570 stopped * thread #1: tid = 0x113a8, 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT frame #0: 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10 libsystem_kernel.dylib`__pthread_kill: -> 0x7fff89c01286 <+10>: jae 0x7fff89c01290 ; <+20> 0x7fff89c01288 <+12>: movq %rax, %rdi 0x7fff89c0128b <+15>: jmp 0x7fff89bfcc53 ; cerror_nocancel 0x7fff89c01290 <+20>: retq (lldb) 

In case of a critical error, the abort() function must be used. For example, if an error occurred while allocating memory or writing a file. Any further action may aggravate the situation. If you complete the execution in the usual way, which is used to reset the input / output streams, you can lose still intact data and temporary files, so the best solution would be to write a dump and instantly terminate the program.

In the case of a non-critical error, for example, you could not open the file, you can safely exit via exit() .

Setjmp () and longjmp () functions


Here we come to the most interesting - the functions of nonlocal transitions. setjmp() and longjmp() work on the goto principle, but unlike it, they allow you to jump from one place to another within the entire program, rather than one function.

<setjmp.h>


int setjmp(jmp_buf env);

Stores information about the execution context of a program (microprocessor registers, etc.) to env . Returns 0 if it was called directly or value , if from longjmp() .

void longjmp(jmp_buf env, int value);

Restores the execution context of a program from env , returns control to setjmp() and passes it value .

Example:

 /* // main.c // setjmp simple // // Created by Ariel Feinerman on 18/02/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <setjmp.h> static jmp_buf buf; void second(void) { printf("second : before longjmp()\n"); // prints longjmp(buf, 1); // jumps back to where setjmp was called – making setjmp now return 1 printf("second : after longjmp()\n"); // does not prints // <- Here is the point that is never reached. All impossible cases like your own house in Miami, your million dollars, your nice girl, etc. } void first(void) { printf("first : before second()\n"); second(); printf("first : after second()\n"); // does not print } int main(int argc, const char * argv[]) { if (!setjmp(buf)) first(); // when executed, setjmp returned 0 else // when longjmp jumps back, setjmp returns 1 printf("main\n"); // prints return EXIT_SUCCESS; } 

Conclusion:

 first : before second() second : before longjmp() main 

Using setjmp() and longjmp (), you can implement an exception mechanism. In many high-level languages ​​(for example, in Perl ), exceptions are implemented through them.

Example:

 /* // main.c // exception simple // // Created by Ariel Feinerman on 18/02/17. // Copyright 2017 Feinerman Research, Inc. All rights reserved. */ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <setjmp.h> #define str(s) #s static jmp_buf buf; typedef enum { NO_EXCEPTION = 0, RANGE_EXCEPTION = 1, NUM_EXCEPTIONS } exception_t; static char *exception_name[NUM_EXCEPTIONS] = { str(NO_EXCEPTION), str(RANGE_EXCEPTION) }; float asin_e(float num) { if ((num < 1.0f) && (num > -1.0f)) { return asinf(num); } else { longjmp(buf, RANGE_EXCEPTION); // | @throw } } void do_work(float num) { float res = asin_e(num); printf("asin(%f) = %f\n", num, res); } int main(int argc, const char * argv[]) { exception_t exc = NO_EXCEPTION; if (!(exc = setjmp(buf))) { // | do_work(-3.0f); // | @try } // | else { // | fprintf(stderr, "%s was hadled in %s()\n", exception_name[exc], __func__); // | @catch } // | return EXIT_SUCCESS; } 

Conclusion:

 RANGE_EXCEPTION was hadled in main() 

Attention! The setjmp() and longjmp () functions are primarily used in system programming, and their use in client code is not recommended. Their use worsens the readability of the program and can lead to unpredictable errors. For example, what happens if you jump not up the stack - into the calling function, but into a parallel one that has already completed its execution?

Information


Source: https://habr.com/ru/post/324642/


All Articles