preface
Errors and exceptions:
Errors and exceptions are errors that occur during program compilation or running. The difference is that exceptions can be caught and handled by developers; Errors generally do not need to be handled by developers (and cannot be handled), such as memory overflow. If exceptions are not handled in time, errors may also occur.
During development, it is inevitable to handle exceptions, such as exceptions during function calls:
- It doesn't mean mistakes in function design
- But a predictable branch of abnormal function
Example:
char* strcpy(char* des,const char* source) { char* r=des; assert((des != NULL) && (source != NULL)); while((*r++ = *source++) != '\0'); return des; }
Significance of exception handling:
- In the process of software development, exceptions are handled most of the time
- Exception is not an error, but it may cause the program to not run normally
- Exception handling directly determines the robustness and stability of software
1, Abnormal expression
In C language, exceptions are usually represented by error codes, such as error codes in Linux
Advantages: simple error code definition and easy to use
Disadvantages: the same error code has the same meaning in different programs
Errno | Errno number | explain |
---|---|---|
EINTR | 4 | System call interrupt. |
EAGAIN | 11 | The resource is temporarily unavailable. |
EBUSY | 16 | The resource is busy. |
EMFILE | 24 | Each process file descriptor table is full. |
EPIPE | 32 | The pipe is disconnected. |
General design method of exception representation: integer partition is used to represent exceptions
bit31 | bit30 - bit16 | bit15-bit0 |
---|---|---|
1 | Module identification | Error identification |
The highest bit is the sign bit. If it is fixed to 1, all error codes are negative.
err_def.h defines exception ID and module ID
#ifndef ERR_DEF_H #define ERR_DEF_H #include <stdio.h> #include <stdint.h> #include <stdbool.h> typedef int32_t err_t; // Number of modules #define MODULE_COUNT 2 // Setting the highest bit to 1 constitutes an error code #define ERR_CONSTRUCT(_error_) ((_error_) | (1 << 31)) // Confirm the value of the first error ID according to the module ID #define ERR_CODE_BEGIN(_module_) ((_module_) << 16) // Get module ID #define ERR_GET_MODULE_ID(_error_) (((_error_) >> 16) & 0x7fff) // Gets the subscript of the error ID in the array #define ERR_GET_ERROR_INDEX(_error_) ((_error_) & 0xffff) // Get error ID #define ERR_GET_ERROR_CODE(_error_) ((_error_) & 0x7fffffff) typedef enum { TIMER_MODULE, LCD_MODULE } module_enum_t; typedef enum { TIMER_ERR_OK = ERR_CODE_BEGIN(TIMER_MODULE), TIMER_ERR_MEM, TIMER_ERR_BUF, TIMER_ERR_TIMEOUT, TIMER_ERR_VAL } timer_err_enum_t; typedef enum { LCD_ERR_OK = ERR_CODE_BEGIN(LCD_MODULE), LCD_ERR_MEM, LCD_ERR_BUF, LCD_ERR_TIMEOUT, LCD_ERR_VAL } lcd_err_enum_t; #endif
2, Exception report
Generally, system log is the main form of reporting exceptions, but exception reporting is not handling exceptions. Exception reporting is only responsible for recording, and exception handling is used to prevent the crash of exception programs.
Exception report example:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "err.h" // Output exceptions #define LOG(errno) printf("[%s:%d] Errno: %x\n", __FILE__, __LINE__, errno) err_t lcd_init() { err_t ret = ERR_CONSTRUCT(LCD_ERR_TIMEOUT); return ret; } int main() { err_t ret = lcd_init(); if(LCD_ERR_OK != ret) { LOG(ret); } }
The results are as follows: print the error code directly, but we may have to find the specific error information ourselves.
Printing the exception identification and exception directly is not friendly and inconvenient to define the problem. Therefore, we hope to have a more intuitive exception output method:
- Directly output the constant name corresponding to the error code
- The enumeration constant name contains the module name and the module internal error ID
A form similar to the following
#ifndef MODULE_H #define MODULE_H typedef enum { LCD_ERR_OK = ERR_CODE_BEGIN(LCD_MODULE), LCD_ERR_MEM, LCD_ERR_BUF, LCD_ERR_TIMEOUT, LCD_ERR_VAL } lcd_err_enum_t; static const char *lcd_error_str[] = { "LCD_ERR_OK", "LCD_ERR_MEM", "LCD_ERR_BUF", "LCD_ERR_TIMEOUT", "LCD_ERR_VAL" } #endif
However, the error identification may change frequently in the development process, so the corresponding character array should also change. It is very easy to produce errors in the modification process. Special attention should be paid to the use of automatic code generation tools. In the follow-up, we will introduce the use of automatic code generation tools. In this way, we only need to define the corresponding error code, and the string array is automatically generated.
The next thing to do is to associate the error identification character array of all modules
const char* errno_to_str(err_t errno) is converted to the corresponding output:
#include "err.h" static struct error_str_t { // The error code identifies whether the character array exists bool exist; // Last error identification value int last_error; // Array pointer const char ** error_array; }s_error_str_array[MODULE_COUNT]; // Corresponding error identification character array static const char *s_timer_error_str[] = { "TIMER_ERR_OK", "TIMER_ERR_MEM", "TIMER_ERR_BUF", "TIMER_ERR_TIMEOUT", "TIMER_ERR_VAL" }; static const char *s_lcd_error_str[] = { "LCD_ERR_OK", "LCD_ERR_MEM", "LCD_ERR_BUF", "LCD_ERR_TIMEOUT", "LCD_ERR_VAL" }; // Initialize s_error_str_array array void error_str_init() { s_error_str_array[TIMER_MODULE].exist = true; s_error_str_array[TIMER_MODULE].last_error = 4; s_error_str_array[TIMER_MODULE].error_array = s_timer_error_str; s_error_str_array[LCD_MODULE].exist = true; s_error_str_array[LCD_MODULE].last_error = 4; s_error_str_array[LCD_MODULE].error_array = s_lcd_error_str; } // Convert error code to string const char* error_to_str(err_t errno) { static bool initialized = false; // Obtain 16 bit error identification according to the error code uint16_t error_code = ERR_GET_ERROR_INDEX(errno); // Obtain the 15 bit module identification according to the error code uint16_t module_id = ERR_GET_MODULE_ID(errno); if(!initialized) { error_str_init(); initialized = true; } if(errno > 0) return "Errno should less than 0"; if(!s_error_str_array[module_id].exist) return "Error code array isn't exist"; if(s_error_str_array[module_id].last_error < error_code) return "Error code out of range"; return s_error_str_array[module_id].error_array[error_code]; }
Before finding the corresponding string through the module ID and error ID in the code, the s_error_str_array initialization (the generation and initialization of character array can be completed automatically, but certain specifications need to be followed)
Finally, modify the LOG so that it can output more information
// Output exceptions #define LOG(errno) do{ \ const char* str = error_to_str(errno); \ printf("[%s:%d] Errno: %s\n", __FILE__, __LINE__, str); \ }while(0)
In the final result, you can know the error type through the corresponding name
3, Exception handling
Exception handling example:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "err.h" // Output exceptions #define LOG(errno) do{ \ const char* str = error_to_str(errno); \ printf("[%s:%d] Errno: %s\n", __FILE__, __LINE__, str); \ }while(0) err_t lcd_init() { err_t ret = ERR_CONSTRUCT(LCD_ERR_TIMEOUT); return ret; } // Centralized exception handling bool lcd_error_handle(err_t errno) { bool ret = true; err_t err = ERR_GET_ERROR_CODE(errno); switch(err) { case LCD_ERR_MEM: break; default: break; } // If the function cannot handle it, exit ends directly if(!ret) { exit(0); } return ret; } int main() { err_t ret = lcd_init(); if(LCD_ERR_OK != ret) { LOG(ret); lcd_error_handle(ret); } }
Try to report exceptions where exceptions occur to help find the call path when exceptions occur.
Try to unify exceptions in the upper function and deal with exceptions centrally, which helps to improve the maintainability of the code.