English 中文(简体)
是否有更好的方法来拼写错误代码?
原标题:Is There a Better Way to Stringify Error Codes?
This is more of an aesthetic question than a purely functional one, but I want to know if there s any answer. See, I m programming an error reporter for a project I ve recently begun working on, and it s mostly code-based for ease of function use. Think of errno, with various values defined to note specific problems. It s a prevalent system among error handlers. However, I don t want to just give the user a code, because I know from the time before I started programming that a random array of numbers can often be overwhelming when trying to self-diagnose the problem. I want to give them a string representation of the code. However, I cannot think of a pragmatic way to do this. A stringify (#) macro wouldn t work because the error code sent to the function is unknown at compile time. Is there something beyond a large switch that could rectify this issue? For context, this is my little error logger; this is the last step of the error pipeline, if the program gets here, a fatal error has occurred. // Notes: We don t do a lot of error-checking in this function, beyond // the stuff built into PushNotification. The reason for that is // fairly simple; at this point, we don t give a darn. If the program // gets here, something s irreversibly wrong regardless. // Last edit: July 3rd, 2024 _Noreturn void InternalLogError(const char* caller, const char* file, u32 line, u32 code) { char error_message[128]; // This is the place in which I want to add the code-to-string bit. snprintf(error_message, 128, " Memphim failed catastrophically. " "Code: %d. Caller: %s @ %s Line: %d ", code, caller, file, line); if (CheckShellAvailability()) puts(error_message); else PushNotification("Memphim Error Reporter", error_message); exit(EXIT_FAILURE); }
最佳回答
A string look-up table would be one obvious way to do it. typedef enum { ERR_NONE, ERR_SERVERS_ON_FIRE, ERR_LIVE_BEAVERS, ERR_N // total number of supported errors } err_t; static const char* err_str[] = { [ERR_NONE] = "No error", [ERR_SERVERS_ON_FIRE] = "The servers are on fire", [ERR_LIVE_BEAVERS] = "Live beavers in the server room", }; static_assert(sizeof(err_str)/sizeof(*err_str) == ERR_N, "err_t and err_str are not consistent with each other"); ... void InternalLogError(..., err_t code) { puts(err_str[code]); } Or in case avoiding storing the values at separate places must be avoided, the X-macro version: #define ERR_LIST(X) /* code str */ X(ERR_NONE, "No error") X(ERR_SERVERS_ON_FIRE, "The servers are on fire") X(ERR_LIVE_BEAVERS, "Live beavers in the server room") typedef enum { #define ERR_T_ENUM(code, str) code, ERR_LIST(ERR_T_ENUM) ERR_N // total number of supported errors } err_t; static const char* err_str[] = { #define ERR_STR_LUT(code, str) [code] = str, ERR_LIST(ERR_STR_LUT) };
问题回答
For the sake of seeing an alternative, here is a variation on @Lundin s very good answer. This inverts the use of macros to achieve an analogous result. // errors.tbl - NB: no header guards! #ifdef ERR_AS_STR const char *errStrs[]; static const char *errToStr( int code ) { return errStrs[code]; } static const char *errStrs[] = { # define X(e,s) s #else enum { # define X(e,s) e #endif X( ERR_NONE, "No error" ), X( ERR_SERVERS_ON_FIRE, "The servers are on fire" ), X( ERR_LIVE_BEAVERS, "Live beavers in the server room" ), X( ERR_N, NULL ) # undef X }; and // main.c #include #include "errors.tbl" // once as enums #define ERR_AS_STR #include "errors.tbl" // and now as array of strings (and getter) #undef ERR_AS_STR int main( void ) { for( int i = ERR_NONE; i < ERR_N; i++ ) puts( errToStr( i ) ); return 0; } This is merely a minimal working prototype, to be adapted to suit requirements with regard to the visibility of the enum, the array and the "getter" function.
On some project, I decided I want to fix a similar problem. I decided that defining error code and error text in multiple places is not for me, I want to have all in one place. Instead, I decided that I will define error code and description in one place and preprocess the project files to extract the text and generate error codes to text table. Consider the following source code file: #include int some_library_function() { return ERROR_GEN(ERROR_SOME_ERROR, "This is error description") } Before compilation, a CMake or python script went through all the files. For each file containing ERROR_GEN, two files were generated - errorgen_list.h and errorgen_str.h. errorgen_list.h contained all the enums collected from ERROR_GEN functions. errorgen_str.h contained a "This is error description", a table to transform error code to a string. The script looks something along: def get_all_errors_from_sources(): rereplace = re.compile( r ERROR_GENs*(s*(.*)s*,s*(".*")s*)s*; , flags=re.MULTILINE, ) dir = os.path.dirname(__file__) errors: List[Err] = [] for path in Path(dir).glob("**/*.c"): for line in open(path).read(): res = rereplace.findall(line) if res: errors += [Err(*res)] return errors def prepare_sources(errors): enumout = "/* @file */ " for k, v in errors: enumout += " " + k + ", /// " + v + " " msgout = "/* @file */ " for k, v in errors: msgout += " " + v + ", // " + k + " " return (enumout, msgout) errors = get_all_errors_from_sources() enumout, msgout = prepare_sources(errors) with Path("errorgen_list.h", "w") as f: f.write(enumout) with Path("errorgen_str.h", "w") as f: f.write(msgout ) Then with the following errorgen.h: enum errorgen_errors_e { ERRORGEN_OK = 0, ERRORGEN_START = -1000, #include "errorgen_list.h" ERRORGEN_STOP, }; #define ERROR_GEN(a, b) a const char *error_to_str(int); And the following errorgen.c: static const char *const errorsgen_str[] = { #include "errorgen_str.h" }; const char *error_to_str(int err) { // error handling... return errorsgen_str[err - ERRORGEN_START - 1]; } I could easily keep error code with error string together in the source code. This resulted in multitude of error codes and easier to track them and more readable error codes. The proper dependency has to modelled in the build system, so that the build system knows to generate errorgen_* before compiling the files.




相关问题
Simple JAVA: Password Verifier problem

I have a simple problem that says: A password for xyz corporation is supposed to be 6 characters long and made up of a combination of letters and digits. Write a program fragment to read in a string ...

Case insensitive comparison of strings in shell script

The == operator is used to compare two strings in shell script. However, I want to compare two strings ignoring case, how can it be done? Is there any standard command for this?

Trying to split by two delimiters and it doesn t work - C

I wrote below code to readin line by line from stdin ex. city=Boston;city=New York;city=Chicago and then split each line by ; delimiter and print each record. Then in yet another loop I try to ...

String initialization with pair of iterators

I m trying to initialize string with iterators and something like this works: ifstream fin("tmp.txt"); istream_iterator<char> in_i(fin), eos; //here eos is 1 over the end string s(in_i, ...

break a string in parts

I have a string "pc1|pc2|pc3|" I want to get each word on different line like: pc1 pc2 pc3 I need to do this in C#... any suggestions??

Quick padding of a string in Delphi

I was trying to speed up a certain routine in an application, and my profiler, AQTime, identified one method in particular as a bottleneck. The method has been with us for years, and is part of a "...

热门标签