C++ Logo

std-proposals

Advanced search

Re: [std-proposals] The Lakos rule, undefined behaviour and noexcept

From: Sebastian Wittmeier <wittmeier_at_[hidden]>
Date: Sat, 26 Sep 2026 23:37:30 +0200
Whether nullpointer arithmetic and dereferences are a good example of exceptions or not, this is an application of the Lakos rule for precondition violations.   And as alternative to Lakos a look into exceptions, which don't have to be specified (the functions can have noexcept) and which don't terminate with noexcept.   -----Ursprüngliche Nachricht----- Von:Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]> Gesendet:Fr 25.09.2026 17:10 Betreff:[std-proposals] The Lakos rule, undefined behaviour and noexcept An:std-proposals <std-proposals_at_[hidden]>; CC:Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>; In order to try get my head around all this "Lakos rule" stuff and to try enable in-depth discussion about it, I've added a new ABI to the GNU compiler: -m64nullex The 'm64nullex' ABI is the same as 'm64', but with three changes. Normally in C++, the following will just result in undefined behaviour:    - Dereferencing a nullptr    - Performing arithmetic on a nullptr    - Passing a nullptr to a Standard C library function Let's have a quick little talk about what undefined behaviour really is. I remember a few decades ago, someone came out with the line "make demons fly out of your nose" -- their aim being to convey that the program can do anything at all. So to give an example, let's say that you have a program with 4 threads, and let's say in one of the threads, you simply do:    int *p = nullptr;    ++p; Even though you've only done this in one thread, and even though you haven't done any invalid memory accesses (no reads, no writes), the other 3 threads can now just do whatever they want... so if one of those other threads was just about to do "format d:", well it's now within its rights to do "format c:" instead. So this now begs the question, and please pay particular attention to this:    "Where the ISO C++ Standard says that a particular thing results in undefined behaviour, is it okay for a particular compiler to say that the behaviour is well-defined and that the entire program must continue to function as normal?" Just for added emphasis, let me give you that one more time:    "Where the ISO C++ Standard says that a particular thing results in undefined behaviour, is it okay for a particular compiler to say that the behaviour is well-defined and that the entire program must continue to function as normal?" On the 'm64nullex' ABI, if you do something wrong with a nullptr, it will result in the throwing of an exception. And let me make a point here: "The throwing of an exception is an acceptable form of undefined behaviour". But now there's the sticky part: "Just because the throwing of an exception is an acceptable form of undefined behaviour, does this mean that the entire program must continue to function as normal?" Here are the exceptions I've added to the C++ Standard Library:  namespace std {    struct nullptr_error : exception { ... };    struct nullptr_dereference : nullptr_error { ... };    struct nullptr_arithmetic  : nullptr_error { ... };    struct nullptr_argument    : nullptr_error { ... };  } Therefore if you compile and run the following:    int main()    {        int *p = nullptr;        return *p;    } You'll see:    terminate called after throwing an instance of 'std::nullptr_dereference'      what():  nullptr dereference    Program terminated with signal SIGABRT (6) GodBolt: https://godbolt.org/z/Pjab76b5o And if you run the following:    int main()    {        strcpy(nullptr, nullptr);    } You'll see:    terminate called after throwing an instance of 'std::nullptr_argument'      what():  nullptr argument    Program terminated with signal SIGABRT (6) GodBolt: https://godbolt.org/z/v6vzGbsK7 And if you run the following:    int main()    {        char buf[] = "I won't eat icecream.";        char *p = strchr(buf, 'z');        p[2] = 'd';    } You'll see:    terminate called after throwing an instance of 'std::nullptr_arithmetic'      what():  nullptr arithmetic    Program terminated with signal SIGABRT (6) GodBolt: https://godbolt.org/z/Y5cEW5h35 Okay so now that we have this cool new compiler, let's play around with it. Let's write an efficient function to copy a string:    void StrCopy(char *p, char const *q)    {      while ( *p++ = *q++ );    } Let's document this function to say that you shouldn't pass nullptr's to this function, and that the behaviour is undefined if you do give it nullptr's. Now now now now now now............... what to do here. Should we stick 'noexcept' on our StrCopy? Let's consider a few things.  (1) - If we do not mark this function as 'noexcept', and if we give it a nullptr, it will throw std::nullptr_error. Nothing too controversial here.  (2) - If we mark this function as 'noexcept', and if we give it a nullptr, then what should happen? There are two different things we could do in the case of No. 2:  (2.a) Call std::terminate or:  (2.b) Let the exception escape the function even though the function is marked as 'noexcept' -- note that this would not be so questionable given that the escape of an exception from a 'noexcept' function is an acceptable form of undefined behaviour. And should the entire program continue to function as normal after this exception escapes? So let's stick 'noexcept' on our function:    void StrCopy(char *p, char const *q) noexcept    {      while ( *p++ = *q++ );    } Currently with my 'm64nullex' ABI, if you use this function as follows:    #include <cstdio>    // puts    void StrCopy(char *p, char const *q) noexcept    {        while ( *p++ = *q++ );    }    int main()    {        try        {            StrCopy(nullptr, nullptr);        }        catch(...){}        std::puts("Last line in main");    } You'll see:    terminate called after throwing an instance of 'std::nullptr_arithmetic'      what():  nullptr arithmetic    Program terminated with signal SIGABRT (6) You can see that 'std::terminate' has been called. Let me suggest a crazy half-baked idea here though... what if 'nullptr_error' were to be given special status among the exception types? What if a 'nullptr_error' exception could escape a function marked 'noexcept', but every other exception type would result in the calling of 'std::terminate'? Perhaps this would be achieved by introducing a new exception base class called "std::escapee", and so then we'd have:    class nullptr_error : public std::exception, public std::escapee {}; And so if you have a function marked as 'noexcept', and if an exception tries to escape, check if the exception is derived from 'std::escapee', and if it is, let it escape -- otherwise call std::terminate. Is this too crazy? Basically when the compiler encounters the following:    void Func(void) noexcept    {        . . .    } It compiles it as though it were written:    void Func(void) noexcept(false)    {        try        {            . . .        }        catch(std::escapee const &)        {            throw;        }        catch(...)        {            std::terminate();        }    } I'm just throwing out ideas here to get discussion going. -- Std-Proposals mailing list Std-Proposals_at_[hidden] https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2026-09-26 21:44:41