:: Re: [DNG] C exception handling
Etusivu
Poista viesti
Vastaa
Lähettäjä: Didier Kryn
Päiväys:  
Vastaanottaja: dng
Aihe: Re: [DNG] C exception handling
Le 27/10/2024 à 13:39, nick a écrit :
> Karl, Didier,
>
> I can definitely see both sides of this issue. There is probably a
> place for error handling macros. To see a non trivial codebase where
> error handling macros are used throughout:
> https://github.com/espressif/esp-idf
> They have macros like ESP_RETURN_ON_FAILURE() and similar macros for
> goto on failure and with or without logging. Most functions return
> esp_err_t and logging is standardized based on a static symbol called
> TAG in each file. These conventions make it manageable.
>
> Having said that, when I am writing embedded code to run under IDF, I
> don't use their macros. I write things out the long way because I find
> it hard to remember all their combinations and I find the long way
> more readable. Also I am currently working on code that can run with
> or without IDF depending on the MCU I'm compiling for. So I use my
> conventions. If I was to submit PRs to the IDF repo then I'd have to
> use IDF conventions though. So it's case by case.
>
> I also want to share a technique that I picked up in the university
> research environment. I worked with a dude who was a kind of a wayward
> genius, he was quite lazy and sloppy and had an abrasive personality
> but he was super productive, he wrote a lot of papers and won awards
> and stuff so I could overlook his peccadillos. In research you are
> basically testing an idea so it's good to smash out a lot of code
> quickly and avoid gold plating (a term that was bandied about a lot --
> and as a perfectionist I find it very conflicting, but I digress).
> Anyway so this guy would use assertions instead of proper error
> checking but with a differently named macro:
>   char *p = strdup("This is a string");
>   rassert(p != NULL);
> The name rassert() means "run time assert" and it is an assertion that
> is always present, even in release builds, ie a failure does not
> indicate a coding error but an issue with the external environment
> that we do not control. I usually create the rassert() macro by
> hacking on the system's assert.h to create a copy called rassert.h
> that ignores the NDEBUG processor symbol (that is defined in a release
> build to suppress assertions). This way I can reuse the system's
> assert handler and get nicely printed messages. And when something
> does go wrong it quickly takes me to the failing code. So it's exactly
> like an uncaught exception really.
>
> I don't like the idea of using setjmp/longjmp routinely for this kind
> of purpose though. That's because setjmp/longjmp are implemented in a
> kind of a hacky way where the stack pointer and certain registers are
> stored in the jmp_buf structure at setjmp time (actually an array) and
> restored at longjmp time. But this has some weird consequences.
> Register variables will revert to their values from setjmp time, as
> longjmp doesn't know where their real values are saved. And if the
> setjmp call occurred within an expression then it will try to
> calculate the expression again at longjmp time but the partial results
> will have been corrupted in the meantime by the reuse of the stack. I
> believe current C standards place restrictions on how setjmp can be
> called for that reason. And probably modern compilers suppress most
> optimizations in routines with setjmp.
>
> For a while I toyed with the idea of making a source to source
> translator that could add exception handling to C using setjmp/longjmp
> but eventually I decided a safer way would be to munge every function
> signature so that the original return value becomes an out parameter
> (the caller provides a pointer to say where they want the function
> result placed) and the return value instead gives the error status --
> which could be a simple bool, an errno similar to unix errno or
> esp_err_t mentioned above, or a pointer to an exception object. The
> required translation is not trivial though, as expressions containing
> function calls must be broken down into separate statements and when
> they occur within a control structure like a C for-loop, this has to
> be emulated using gotos or similar means.
>
> Overall, the easiest way IMO is to write out the error handling code
> longhand. Or use Python, Ada, etc. Sigh. I love C but there is
> definitely a love/hate aspect to it sometimes.


    So you've tried several ways to workaround the absence of good
exception handling in C; kudos. But definitely, C is not meant as a
high-level language; it is kind of a super omni-arch assembler. It was
designed as something minimalist, and therefore cannot provide a
separate execution flow for exceptions.

    Actually, I'm not dealing with the same issue as you. The
applications I write in C are small enough to not require a runtime
autodiagnostic of bugs. BTW, in Ada this isn't an issue at all. Runtime
exceptions are seldom and diagnosed in great detail (actually
essentially one ever happens: constraint_error). Typically, in the
development of an application in Ada, the debuging step is about your
own logic errors, not bugs leading to exceptions. The bugs are avoided
because the very language make them unlikely and easily detectable at
compile time.

    But, when I started this thread, I was talking of exceptions raised
by my own logic when it discovers something unexpected in the data
stream. Propagating the errors through the return value is one of the
problems, as you have mentionned, but even worse is putting a
conditional branching after every function call, because it shows error
detection more strikingly than the normal flow: every line of code is
primarily dealing with error detection instead of the normal algorithm,
which, in itself migh be complicated enough. In reality errors are
exceptional (that's why we call them exceptions) and their management
should be separate instead of clobering the program of normal operation.
This is the problem I'm dealing with: make my code readable. Do you
understand what I mean?

--     Didier