Date: Fri, 25 Sep 2026 16:09:59 +0100
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.
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.
Received on 2026-09-25 15:10:15
