I also have a interesting sample with linked list
Speaking generally rather than specifically about this code, your
extra checks are redundant when the code is run from a single thread,
and insufficient if it is later used from multiple threads (accessing
the same data). But extra checks can fool people into thinking it is
safe - since the checks are unnecessary for sequential code they must
have been put there to catch rare race conditions.
There are times when you want seat-belts /and/ airbags. But focusing
on that instead of making sure the driver is competent is not helpful!
In Cake (my front-end), I made `assert` a special built-in function to
allow it to be used by static analysis.
`assert(p != 0);`
Static analysis will then assume that `p` is not zero after this line.
It will (or at least, it can), if the assert is enabled. If NDEBUG is
defined then the standard assert macro does nothing - it does not tell
the compiler anything. And compilers - baring bugs, as always - do not
optimise by guessing assumptions.
Does assert overrides what the compiler already knows?
If you tell the compiler two contradictory things, you can't expect good
results. It is reasonable for it to believe the first thing you said,
or the second, or neither, or both at different times. Ideally, of
course, it will give a warning.
Do not lie to your compiler. It will result in tears - /your/ tears,
not the compiler's.
int i = 1;
assert(i == 2);
Here, the compiler knows `i` is `1`, but the `assert` (i.e., the
programmer) is claiming that it’s `2`!
You can't expect good results from nonsense - at best, you can hope for
a warning.
This is one reason why I use my own macro:
extern void __attribute__((error("Assume failed"))) assumeFailed(void);
#define Assume(x) \
do { \
if (__builtin_constant_p(x)) { \
if (!(x)) { \
assumeFailed(); \
} \
} \
if (!(x)) __builtin_unreachable(); \
} while (0)
(It's actually a bit more complicated since, like standard assert(), it
supports enabling or disabling. But that's the key part.)
This will catch the mistake in your example, if you use "Assume" instead
of "assert".
I then reconsidered my view. `Assert` is about what the programmer is
stating to be true. (Programmer can be wrong) It is not a "override
command".
I think this is applicable for any "assume concept".
C++ 23 added [[assume( expression )]]
"Tells the compiler to assume that an expression will always evaluate to
true at a given point ..." "The purpose of an assumption is to allow
compiler optimizations based on the information given"
"IMPORTANT: DANGEROUS OPERATION, USE WITH CARE"
https://en.cppreference.com/w/cpp/language/attributes/assume
"[[assume(E)]];" in C++23, or "__attribute__((assume(E)));" as an
extension in newer gcc in any C or C++ standard, is basically the same
as "if (!(E)) __builtin_unreachable();".
My "Assume" macro is better in most cases, because it catches errors
that are seen at compile time, while the assume attribute would
eliminate code with no warning.
I also may add this [[assume]] to cake, but assert also gives me a
runtime check.
In the first example `assert(p != 0)`, the compiler knows that `p` is
MAYBE `null`. But the programmer is asserting that `p` cannot be `null`,
so the compiler will follow the programmer's assertion.
Perhaps the programmer knows something the compiler doesn’t?
If you want the best from your compiler (optimisations and error
checking), tell it what you know.
This is similar to a case with a linked list, where the programmer knew
that if the `head` is `null`, then the `tail` must also be `null`.
However, if the compiler is certain about a fact and encounters an
`assert` with conflicting information, it should trigger a warning.
An "assert" macro could be defined to do that, if you are happy to use
the gcc extension "__builtin_constant_p()". If not, then I don't think
it is possible to specify that it should trigger a warning in any clear
way unless the expression is a constant expression - and then you'd be
using static_assert().
int i = 0;
assert(i != 0); // Warning
So make a better "assert" that does what you want.
When the compiler finds an assert, that is not adding extra information
but only saying what the compiler already knows, then we don't have
warnings. But, this is a redundant check.
int i = 0;
assert(i == 0); // ok
On the other hand, redundant 'if' I have a warning.
if (i == 0) {} //warning 'i' is always 0 here!
So, basically.
- if assert has a conflicting information compared with what compiler
already knows, then it is a warning. Compiler will not override what it
knows?
- If the assert gives the exactly information compiler already have,
then is is not a warning. Both compiler and programmers knows the same
thing.
- If the compiler is not sure about a fact and the programmer gives a
plausible fact then the compiler will assume the programmer is correct.