The assert() macro is a very useful tool to document invariants that are supposed to be true at a certain point in the code. Moreover, they also help the programmer to find bugs since it is expanded to an error-checking code in debug mode. However, in release mode (NDEBUG), this macro is expanded to ((void)0). For me, that specific definition of assert leads to a loss a useful information that could be used by the optimizer.
For instance, if the optimizer was assert-aware, it would be almost possible to emulate the restrict keyword of C99 :
void fn_c99(double * restrict a, double * restrict b) { ... }
void fn_cpp(double * a, double * b) { assert(a!=b); ... } // (note: for equivalence, a & b must not be arrays)
This is only one example, but we can easily imagine more situations where it could be useful to explicit various constraints (inequalities, !=nullptr, ...) that would help the optimizer (to remove useless branches for instance).
By expliciting all the contraints that we have on the variables:
1- We're giving new hints to the optimizer that it cannot prove itself (in release mode)
2- We can check the conformance of our code to these assertions (in debug mode)
The current definition of assert() doesn't allow this kind of optimization since the compiler doesn't even see the assertions (they are removed by the pre-compiler if NDEBUG is defined). Two little modifications of the standard would be enough to enable it: let the expansion of assert() be implementation-defined in release mode + allow assert-based optimizations.
What do you think about this proposal?
The assert() in release mode is defined to be a no-op. As far as I know, the compiler is only allowed to make optimizations if the output preserves the semantic.#define NDEBUG#include <cassert>int fn(int a) {assert(a==2);return a*2;}In this code, assert() is required by the standard to expand to nop. Here the assert() 's semantic is "do nothing", because NDEBUG is defined. So I could perfectly call fn(3) and expect it to return 6. Consequently, fn is equivalent to its non-optimized version :int fn(int a) {return a*2;}My proposal is to slightly change the semantic of assert() in order to allow this kind of optimization.
constexpr bool undefined_behavior() noexcept;
#ifdef NDEBUG
# define ASSERT( PRED ) static_cast<void>( (PRED) || undefined_behavior() ) #endif
W dniu wtorek, 27 listopada 2012 21:26:43 UTC+1 użytkownik Frédéric TERRAZZONI napisał:The assert() macro is a very useful tool to document invariants that are supposed to be true at a certain point in the code. Moreover, they also help the programmer to find bugs since it is expanded to an error-checking code in debug mode. However, in release mode (NDEBUG), this macro is expanded to ((void)0). For me, that specific definition of assert leads to a loss a useful information that could be used by the optimizer.
For instance, if the optimizer was assert-aware, it would be almost possible to emulate the restrict keyword of C99 :
void fn_c99(double * restrict a, double * restrict b) { ... }
void fn_cpp(double * a, double * b) { assert(a!=b); ... } // (note: for equivalence, a & b must not be arrays)
This is only one example, but we can easily imagine more situations where it could be useful to explicit various constraints (inequalities, !=nullptr, ...) that would help the optimizer (to remove useless branches for instance).
By expliciting all the contraints that we have on the variables:
1- We're giving new hints to the optimizer that it cannot prove itself (in release mode)
2- We can check the conformance of our code to these assertions (in debug mode)
The current definition of assert() doesn't allow this kind of optimization since the compiler doesn't even see the assertions (they are removed by the pre-compiler if NDEBUG is defined). Two little modifications of the standard would be enough to enable it: let the expansion of assert() be implementation-defined in release mode + allow assert-based optimizations.
What do you think about this proposal?
I find such proposal very useful. It would enable some assert-based optimizations. This suggestion was raised years ago by Niklas Matthies (see here). It requires that there is some intrinsic function that represents an undefined behavior that the compiler is aware of:
constexpr bool undefined_behavior() noexcept;
When compiler sees it, it is guaranteed that the function will trigger an undefined behavior
constexpr bool undefined_behavior() noexcept;
When compiler sees it, it is guaranteed that the function will trigger an undefined behavior
How exactly do you "guarantee" undefined behavior? It's undefined; by definition, anything can happen.
constexpr bool undefined_behavior() noexcept;
When compiler sees it, it is guaranteed that the function will trigger an undefined behavior
How exactly do you "guarantee" undefined behavior? It's undefined; by definition, anything can happen.
Exactly - how can you _not_ guarantee it? Whatever happens next, satisfies "undefined". :-)
Basically, in the standard say something like "the behaviour of std::undefined_behaviour() is undefined".
And thus after that function is called, the compiler is free to do whatever it likes with the code.
int passengers = getPassengers();
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
case 2:
return handleEven( passengers );
default:
impossible_to_get_here();
// equivalent to assert(false);
}
Would allow the compiler to rearrange the code to:
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
default:
return handleEven( passengers ); }
Even though as-if rule would not allow that.
@Andrzej Krzemienski:Like Martinho, I don't understand why such a primitive should be exposed (undefined_behavior() or impossible_to_get_here()).It is much more simple to say right in the standard :- If NDEBUG is undefined, then assert(false) abort the program.- If NDEBUG is defined, then assert(false) is UB (instead of doing nothing, in the current version)The actual definition of the assert() macro should be left as an implementation detail. Compared to the current version of assert(), I do not see any drawback : it won't break code that is not already-broken, and it gives an easy way to help the compiler producing better output.
@Lawrence Crowl:The contract programming proposal is indeed better to deal with class invariants & pre/post-conditions. But it may not be always the best solution, especially if we are inside a function/method and we're asserting things about intermediary results.
It's worth noting that this kind of code could be warned as "potentially dangerous" by the compilers if the assert was improved :assert(condition);if(!condition) { ... } // warning
I dislike this code, and I've (hopefully) never seen this. It looks like a quick "bugfix". If I write a function taking a pointer argument, I want its "contract" to be the same no matter I've defined NDEBUG or not:- If it doesn't accept a nullptr, then I assert(p!=nullptr); and I document it- If it can handle nullptr, then I use a conditionnal statement and I document itConsequently, when this software is running in real word (with NDEBUG naturally), some new vicious code paths are made available. Certainly, some of them have not been properly tested since they're only reachable with NDEBUG. Good luck to debug them afterwards (and no, you cannot disable NDEBUG because the program is likely to abort before the actual bug).It's worth noting that this kind of code could be warned as "potentially dangerous" by the compilers if the assert was improved :assert(condition);if(!condition) { ... } // warningAnyway, I can understand your opinion, and I also understand that some people may want to use it (it is valid C++ code according to the standard after all).
This would avoid the situation where two people inadvertently define either macro with different intentions in mind.
Hello,
having a way to provide some extra information to the compiler looks like a good idea. However, I disagree with re-using assert for that. There are many places with: assert(expensive_check_that_structure_is_valid()); where you clearly don't want to run the check in release mode, and the compiler wouldn't be able to remove the check if the preprocessor didn't do it (some compiler magic might be possible so it doesn't actually execute the code, but then it wouldn't be implementable in terms of the existing __builtin_unreachable()).
if (x == 0) {
//...
}
else {
assert(x != 0);
}
int passengers = getPassengers();
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
case 2:
return handleEven( passengers );
default:
assert(false);
}
Before people are going to seriously use these mechanisms, they
need to be standard. The assert macro is inadequate in a lot of
ways, but everyone uses it precisely because it is standard.
One other example already shown in this thread:
int passengers = getPassengers();
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
case 2:
return handleEven( passengers );
default:
assert(false);
}
Would allow the compiler to rearrange the code to:
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
default:
return handleEven( passengers );
}
Op donderdag 29 november 2012 22:08:28 UTC+1 schreef Andrzej Krzemieński het volgende:One other example already shown in this thread:
int passengers = getPassengers();
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
case 2:
return handleEven( passengers );
default:
assert(false);
}
Would allow the compiler to rearrange the code to:
switch( passengers % 2 ) {
case 1:
return handleOdd( passengers );
default:
return handleEven( passengers );
}For even you'd want case 0, not case 2. For odd you'd generally want != 0, not == 1.
IMO redefining assert (breaking backwards compatibility) just isn't an option.
> In this discussion it was shown that this behavior can be implemented
> without breaking backwards compatibility: by introducing a third "mode" of
> assertions (similar to NDEBUG).
What if a library header depending on the old assert() behaviour is
included in a program that has the new behaviour enabled?
Or code being copied between the two environments? The behaviour would
'silently' change.
What if I want to use both behaviours in the same program?
#define NDEBUG
#include <cassert>
assert(false);
#undef NDEBUG
#include <cassert>
assert(false);
On Fri, Nov 30, 2012 at 11:17 AM, Andrzej Krzemieński
<akrz...@gmail.com> wrote:
>> > In this discussion it was shown that this behavior can be implemented
>> > without breaking backwards compatibility: by introducing a third "mode"
>> > of
>> > assertions (similar to NDEBUG).
>>
>> What if a library header depending on the old assert() behaviour is
>> included in a program that has the new behaviour enabled?
>
>
> I am not sure i understand the situation you are describing. (a short
> example would help.) Normally you put an assert in a CPP file and it is
> compiled separately and assertions work the way you configured them in your
> implementation file.
Some (Boost) libraries are header-only. They're not compiled separately.
They'd not be able to depend on any particular behaviour.
> If a "new program" (i.e. a new translation unit)
> defines macro XNDEBUG its asserts are optimized, but it does not affect the
> behavior of asserts in other translation units. This is similar to the
> current situation where you can define macro NDEBUG only in some translation
> units and the behavior is well defined.
Is it?
If a class defines extra debug data members if NDEBUG isn't present
(think iterator validation for example) you might have ODR violations.
>>
>> Or code being copied between the two environments? The behaviour would
>> 'silently' change.
>
>
> I am sorry. I do not know what you mean here. I guess you say that the
> "behaviour would 'slightly' change" if somewhere I decide to define macro
> XNDEBU. But I do not see how it has anything to do with breaking backwards
> compatibility. "Old porgrams" do not define macro XNDEBUG, so the upgrade of
> the language with "improved" assert cannot affect them.
That's true, but the behaviour of identical code fragments would no
longer be identical.
For no-NDEBUG and NDEBUG that's also the case, but that distinction is
well known, you often have debug and release builds.
>> What if I want to use both behaviours in the same program?
>
>
> You can request different behavior of assert even in the same TU. You can do
> it even today:
>
> #define NDEBUG
> #include <cassert>
> assert(false);
>
> #undef NDEBUG
> #include <cassert>
> assert(false);
I know, but IMO that's seriously ugly.
I'd rather have assert(false); assume(false);
if (c1) {
assert(c2);
//...
}
else {
assume(c2);
//...
}
On Fri, Nov 30, 2012 at 1:50 PM, Andrzej Krzemieński <akrz...@gmail.com> wrote:
>> Is it?
>> If a class defines extra debug data members if NDEBUG isn't present
>> (think iterator validation for example) you might have ODR violations.
>
>
> While I acknowledge the problem you are describing, I believe the problem is
> there already (because we have NDEBUG and non-NDEBUG) and template library
> authors must already address it. The proposal would not make the problem
AFAIK this is addressed by compiling all files with the same defines
and settings.
> worse because "XNDEBUG" is similar to "NDEBUG": in either case you do not
> want to add auxiliary members (unlike for non-NDEBUG).
True, but what behavior should the template authors expect? The old or
the 'new'?
>> That's true, but the behaviour of identical code fragments would no
>> longer be identical.
>> For no-NDEBUG and NDEBUG that's also the case, but that distinction is
>> well known, you often have debug and release builds.
>
>
> I don't know. Perhaps I fail to imagine the situation you are describing
> here. If you use release mode, you can define XNDEBUG in all yout TUs, and I
> cannot imagine how it would cause ODR violation (assuming that your asserts
> do not cause it already in C++03).
This one isn't about ODR violations, it's about expectations for
someone that reads or writes the code.
>> > You can request different behavior of assert even in the same TU. You
>> > can do
>> > it even today:
>> >
>> > #define NDEBUG
>> > #include <cassert>
>> > assert(false);
>> >
>> > #undef NDEBUG
>> > #include <cassert>
>> > assert(false);
>>
>> I know, but IMO that's seriously ugly.
>> I'd rather have assert(false); assume(false);
>
>
> I would see the value of 'assume' only if I had to use both macros in one
> function:
>
> if (c1) {
> assert(c2);
> //...
> }
> else {
> assume(c2);
> //...
> }
>
> But it doesn't look realistic to me.
Why not?
On Fri, Nov 30, 2012 at 9:30 PM, Andrzej Krzemieński <akrz...@gmail.com> wrote:
>> True, but what behavior should the template authors expect? The old or
>> the 'new'?
>
>
> We are talking about assertions. Frederic proposes change in behavior only
> in case assertion fails (well, technically, when the predicate in assertion
> evaluates to false). Template authors should not expect an assertion to
> fail. We put assertions and are sure that they do not fail. If suspect it
Who is we? ;)
True, assertions aren't expected to fail, but they're not guaranteed
to halt the program either. The code that follows might depend on that
last property.
I think examples were posted already.
assert(p != NULL);
if (p == NULL)
return false; // or etc
> might fail, we fix the bug so that we are sure our assertion never fails.
> With this attitude in mind, template authors do not have to care what
> behavior they get if assertion fails, because they are confident it will not
> happen. As Frederic pointed out, when your assertion fails in NDEBUG mode
> you also get an undefined behavior, although at some "higher level of
> abstraction".
I don't think that's always true.
>> This one isn't about ODR violations, it's about expectations for
>> someone that reads or writes the code.
>
>
> Perhaps it is just me, but my primary expectation of asserts is that they
> should never fail. I plant lot of them and are confident (or, I believe)
> that none of the asserts will ever fire. If one fires, especially in
> production (either in NDEBUG or 'XNDEBUG' mode) what code is executed next
> (the one I wrote or the one rearranged by the compiler) would be my least
> concern. Because a failing assert indicates a bug in my code.
Again, I think that's just one use case of asserts.
>> > But it doesn't look realistic to me.
>>
>> Why not?
>
>
> I cannot even imagine what that means, that you 'assert' one thing and you
> 'assume' another. Now if I imagine a guy that is only learning C++ and he is
> told that he sometimes has to assert something in the code, sometimes he has
> to assume something, and sometimes he has to both assert and assume.
> Wouldn't that be too complicated? The way I understand assertions (but
If you don't need the current behavior of assert, you don't need to
use/teach it, do you?
> perhaps this is only me)is that you express something condition that you
> expect never to be true at this point. What the compiler does with it is
> secondary. I.e., in ideal world (where the halting problem is resolved) a
> compiler would verify that my assertions ever fail. Since it cannot do it,
> there is no good action to be taken, so we are given option: check and
> terminate, ignore (for performance reasons), ignore, but check syntax (this
> option is not available). Further optimizations are just another option in
> the set.
>
> In other words, when I plant an assert, in my mind it means "condition C
> will never happen"; it does not mean "when NDEBUG is not defined then
> evaluate C and if it fails call abort; otherwise do nothing". Assert is a
> tool from "declarative" programming rather than "imperative" programming.
> But again, this is probably only my point of view.
That's true for some of my asserts as well, but not for all of them.
>> True, assertions aren't expected to fail, but they're not guaranteed
>> to halt the program either. The code that follows might depend on that
>> last property.
>> I think examples were posted already.
>
>
> "The code that follows might depend on" the property that assertions "are
> not guaranteed to halt the program"? This is how I understood your sentence.
> Is this it right?
Kinda. The compiler can't assume the assertion is true, because assert
isn't guaranteed to abort if it's not.
If assert was guaranteed to abort (basically like my proposed assure),
it could make that assumption.
> Do you mean the situation like this:
> assert(p != NULL);
> if (p == NULL)
> return false; // or etc
Yep
> I.e., "either abort or return"? Then I see what you mean. But is this a
> "valid" use of assertion?
Don't know, but it is used like that in existing code.
> Why do you assert if you know what to do in such
> case?
Maybe you want the debugger to break there.
Maybe the code that follows kinda silently ignores the error, but
you'd still want to know about it in debug builds.
> But would you do this
> "abort of handle" trick in a template library, where the code is exposed to
> the clients? It would be like you (the library author) were imposing certain
> diagnostic measures on the clients, that clients may not wish. Do you know
> any template library that exposes the behavior "abort of handle"?
No, but then again I don't really pay attention to that code.
One example from my own code though:
http://code.google.com/p/xbt/source/browse/trunk/xbt/misc/xbt/to_array.h