Assert and undefined behavior
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Fri Oct 13 02:22:24 UTC 2017
On Thursday, October 12, 2017 21:22:29 kdevel via Digitalmars-d-learn wrote:
> On Thursday, 12 October 2017 at 20:27:03 UTC, Jonathan M Davis
>
> wrote:
> > On Thursday, October 12, 2017 20:15:41 kdevel via
> >
> >> ---
> >> void main ()
> >> {
> >>
> >> assert (false);
> >>
> >> }
> >> ---
> >>
> >> qualifies as "invalid, and therefore has undefined behaviour."
> >> A statement, which makes no sense to me. Either it is a
> >> "debugging aid", that implies defined behavior, or it is
> >> undefined behavior, then assert (false) cannot aid debugging.
> >
> > assert(false) is a bit special in that it's never removed (it
> > becomes a HLT instruction with -release),
>
> Confirmed. I should have written something like this instead:
>
> ---
> import std.stdio;
> import std.string;
> import std.conv;
> void main ()
> {
> int i;
> i = readln.chomp.to!int;
> assert (i != 3);
> writeln ("i = <", i, ">");
> }
> ---
>
> Is it defined that this program throws an AssertError in debug
> mode if 3 is fed to stdin? If not, assert (...) could not aid
> debugging.
If assertions are compiled in (which they are if you're not compiling with
-release), and i is ever 3, then an AssertError will be thrown. This is
guaranteed. As such, the compiler is free to assume that i is never 3 when
code execution arrives at the line after the assertion, and if it can do an
optimization based on that fact, it is free to do so. You've told it that i
should never be 3 at that point and that it's a bug if it is, and as such,
it is free to assume that i is never 3 after the assertion even if the
assertion is compiled out with -release - that is the only place that
undefined behavior may enter into it. If the compiler does an optimization
based on the fact that i isn't 3, and it is, and -release is used, then you
could get some weird behavior when the code reaches the lines after the
assertion - but by definition, you already have a bug if i is 3, and your
program in general is assuming that i isn't 3 at that point, so you're going
to get bad behavior either way. The fact that your assertion failed means
that you have a logic error in your program, and it is therefore in an
invalid state and will likely not behave correctly.
However, your example is an excellent example of when _not_ to use
assertions. Assertions should never be used on user input or anything
outside of the program's control. When you use an assertion, you are saying
that it is a bug in the progam if that assertion fails, and bad user input
isn't a bug, though the fact that you're not validating user input arguably
is (certainly it is if the assertion is there, since at that point, you're
saying that it's a bug if i is ever 3). Assertions allow you to catch bugs
in your logic during development and then don't slow your program down when
compiling with -release for production. They are not for validating anything
other than that the logic of your program is correct.
And if for any reason, you're paranoid enough that you want those logic
checks to still be there in production, then either don't use -release (even
in production), or do something like
enforce!Error(cond, "msg");
instead of
assert(cond, "msg);
and then you'll get an Error thrown when the condition fails - even with
-release.
On a side note, I would point out that talking about "debug mode" with D
gets annoyingly ambiguous, because that kind of implies the -debug flag,
which has nothing to do with assertions and which actually can be used in
conjunction with -release (all -debug does is enable debug{} blocks), which
is why I try to avoid the term debug mode - though I assume that you meant
when -release isn't used, since that's often what folks mean.
- Jonathan M Davis
More information about the Digitalmars-d-learn
mailing list