From: Gerhard Menzl on
Bob Bell wrote:

> It seems to me that the problem being discussed is largely one of
> definition of terms.

Yes, that's what it has turned into.

> As I understand it, Dave is advocating a definition of precondition
> that reads something like "a condition which must be true when a
> function is called, or else we have undefined behavior." I agree with
> this definition, and view it as synonymous with "a condition which
> must be true when a function is called, or else the function cannot
> continue."

"The function cannot continue" would allow for throwing an exception or
returning an error code, "we have undefined behaviour" would not, hence
the two are not synonymous.

> It sounds like you want a definition that reads something like "a
> precondition is a condition which is tested when a function is called;
> if true, the function runs as normal; if false, the function throws an
> exception."

No, I don't *want* a particular definition. What I was hoping to get is
a clear definition that is consistent with the statement that throwing
an exception upon detecting a precondition violation is almost always
wrong. So far I haven't seen one, and my hopes have dwindled.

As to the role of undefined behaviour, I have seen:

- a violated precondition causes undefined behaviour
- a violated precondition may be the result of undefined behaviour
- once you detect a violated precondition, you already have undefined
behaviour
- continuing after detecting a violated precondition would cause
undefined behaviour

All these are different in subtle ways.

I have also tried to understand whether the definition used by David is
specific to C++ or consistent with the notion of precondition used by
the programming community in general, and with the concept of Design by
Contract in Eiffel in particular.

> Suppose you have a function F() that is documented to throw X when
> condition Y fails. Suppose you call F(), and Y legitimately fails. You
> don't have undefined behavior, an X is thrown, and life goes on.
>
> If, however, F() is called and Y appears to fail because some
> undefined behavior has occured (such as stack corruption or whatnot),
> well now you're in undefined behavior land, and anything could happen.
> The program could crash, wipe your hard disk, or throw an X. If it
> manages to actually throw an X, it doesn't change the fact that your
> program is now exhibiting undefined behavior.
>
> The key point is that your condition Y did not help you detect and
> avoid undefined behavior.

Doesn't the very definition of undefined behaviour render its detection
once it has occurred impossible?

I don't see any point in treating precondition violations separately on
the grounds that they may be the *result* of undefined behaviour: that
is true for any program state, and you cannot detect it anyway. Treating
them separately because going on as if nothing had happened would *lead
to* undefined behaviour is an altogether different story. To me, mixing
these aspects is a major obstacle in this discussion.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Joe on
Interesting how phrasing can be very subtle. To me, preconditions are
those conditions the caller must meet before my routine has well
defined meaning. That is, I am willing to guarantee to the caller that
my routine will work if these conditions are met and further I believe
this is the original intent of DBC. Stack corruption is a different
thing entirely and nothing useful can be done about it. While it is a
violation of the contract, it is not a very interesting one. If the
stack is corrupt, evaluating the precondition is already undefined
behavior and there is no reason the believe that the precondition check
itself will return anything meaningful. I am relatively neutral as to
whether exceptions should be used to return a precondition failure, but
I firmly believe that they have nothing to do with stack corruption
detection and the point is really that the caller is invoking our
routine correctly so that they can correct the offending code if the
preconditions are not true.

In other words, much like static type checking, preconditions are there
to catch programming errors of a dynamic nature, such as accidentally
passing a 5 where the on;y sensible values are between 100 and 500.
Stack corruption is a much more insideous problem which we would be
thrilled to catch with a precondition, but that would not be the reason
I would put a precondition in the code.

joe


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Bob Bell on
Gerhard Menzl wrote:
> Bob Bell wrote:
> > As I understand it, Dave is advocating a definition of precondition
> > that reads something like "a condition which must be true when a
> > function is called, or else we have undefined behavior." I agree with
> > this definition, and view it as synonymous with "a condition which
> > must be true when a function is called, or else the function cannot
> > continue."
>
> "The function cannot continue" would allow for throwing an exception or
> returning an error code, "we have undefined behaviour" would not, hence
> the two are not synonymous.

I should be more specific. I interpret "the function cannot continue"
to mean that the function shouldn't be allowed to execute a single
instruction more, not even to throw an exception.

> > It sounds like you want a definition that reads something like "a
> > precondition is a condition which is tested when a function is called;
> > if true, the function runs as normal; if false, the function throws an
> > exception."
>
> No, I don't *want* a particular definition. What I was hoping to get is
> a clear definition that is consistent with the statement that throwing
> an exception upon detecting a precondition violation is almost always
> wrong. So far I haven't seen one, and my hopes have dwindled.

Then how about a more pragmatic definition? When a precondition fails,
it almost always indicates a programmer error (a bug). When a bug
occurs, the last thing you want is to unwind the stack:

-- unwinding the stack destroys state that could help you track
down the bug
-- unwinding the stack may do more damage
-- throwing an exception allows the bug to go unnoticed if a
caller catches and swallows it (e.g., catch (...))
-- throwing an exception gives a (possibly indirect) caller a
chance to respond to the bug; typically, there isn't anything
reasonable a caller can do to respond to a bug

What you really want is to stop the program in a debugger, generate a
core dump, or otherwise examine the state of the program at the instant
the bug was detected. If you throw an exception, you're just allowing
the program to continue running with a bug.

> I have also tried to understand whether the definition used by David is
> specific to C++ or consistent with the notion of precondition used by
> the programming community in general, and with the concept of Design by
> Contract in Eiffel in particular.

I don't think it is; from what I understand about Eiffel (which is
little) the aim is to keep the program running if a contract is broken.
But I could be wrong about that.

> > Suppose you have a function F() that is documented to throw X when
> > condition Y fails. Suppose you call F(), and Y legitimately fails. You
> > don't have undefined behavior, an X is thrown, and life goes on.
> >
> > If, however, F() is called and Y appears to fail because some
> > undefined behavior has occured (such as stack corruption or whatnot),
> > well now you're in undefined behavior land, and anything could happen.
> > The program could crash, wipe your hard disk, or throw an X. If it
> > manages to actually throw an X, it doesn't change the fact that your
> > program is now exhibiting undefined behavior.
> >
> > The key point is that your condition Y did not help you detect and
> > avoid undefined behavior.
>
> Doesn't the very definition of undefined behaviour render its detection
> once it has occurred impossible?

You're right, undefined behavior, as defined by the language standard,
is undetectable once it's occurred. It's clear from you response that
applying the term "undefined behavior" to preconditions has been
misleading. In the interest of clarity, I'm going to switch to
"undefined state". Example:

F() is documented to specify that it is the responsibility of all
callers to establish condition Y. Now suppose F() is called and Y is
false. What does this mean? All you know is that some caller failed to
establish Y. Assuming the contract was valid and reasonable, you have
detected a bug. (Even if the contract was invalid, you've still
detected a bug -- only the bug is that F() demands condition Y.)

In practical terms, the program has entered an undefined state -- it's
doing something you didn't think it could do. Whether it entered the
undefined state before Y was tested, as a result of testing Y, etc., is
not that important, as far as I'm concerned. What's important is what
you do about it. If you throw an exception, you allow the program to
continue running. But since it's entered an undefined state, you don't
know what it will do.

This is not exactly the same as "undefined behavior" as defined in the
standard, but it shares a lot of similarities. "Undefined behavior"
means the language standard has nothing to say about what the program
will do. "Undefined state" means that you, the programmer, have nothing
to say about what the program will do.

Getting back to my definition from my previous message, a precondition
is "a condition which must be true when a function is called, or else
the program has entered an undefined state." If this happens, I believe
that the right thing to do is stop the program, so I see this as
synonymous with "a condition which must be true when a function is
called, or else the function cannot continue."

> I don't see any point in treating precondition violations separately on
> the grounds that they may be the *result* of undefined behaviour: that
> is true for any program state, and you cannot detect it anyway. Treating
> them separately because going on as if nothing had happened would *lead
> to* undefined behaviour is an altogether different story. To me, mixing
> these aspects is a major obstacle in this discussion.

I think the right way to think about preconditions is that they detect
bugs.

Bob


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Dave Harris on
gerhard.menzl(a)hotmail.com (Gerhard Menzl) wrote (abridged):
> As the author of get_count(), I can tell that at present a violation of
> the precondition will have no consequences, and that undefined
> behaviour will be invoked as soon as the function is changed to
> dereference p. That's what I call a clear idea.

I don't think you /need/ a clear idea. I sometimes assert things I expect
to be true without thinking at all about the consequences if they aren't
true.


> How does the Eiffel runtime handle violated preconditions? Display a
> diagnostic message and abort?

If checking is enable, it throws an exception. If not, then the program
proceeds normally and has undefined behaviour. Programmers are encourage
to write code as if checking is disabled.

-- Dave Harris, Nottingham, UK.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: David Abrahams on
Gerhard Menzl <gerhard.menzl(a)hotmail.com> writes:

>>>By the way, the term "precondition" seems to be used in meanings that
>>>differ from the one you have sketched. In the Eiffel world (as far as
>>>I can tell), a precondition is something that the caller must
>>>guarantee before invoking a function.
>>
>> In my definition the caller must guarantee the precondition also,
>> since the alternative is undefined behavior. I don't know what I've
>> said that could make you think otherwise.
>
> For example:
>
>> Yes, any precondition violation could be the result of stack
>> corruption.
>
> An intact stack is a global condition that cannot be guaranteed a single
> caller.

So? I didn't say that an intact stack is a precondition. I just said
that a corrupted stack could easily result in any precondition being
violated.

> Stack corruption could be the result of a broken compiler or of
> user code that invokes undefined behaviour somewhere else. Both events
> lie outside the language. They cannot be detected reliably and portably
> at this level, and they are not covered by the contract between the
> caller and the called. From this perspective, they're more like Acts of
> God, to extend the law metaphor. Yet you seem to regard such global
> failures in the context of the contract. I find this confusing and at
> variance with the idea of DbC as I understand it.

Let me put it another way: if a single caller cannot guarantee an
intact stack, it cannot guarantee anything. A corrupted stack throws
everything into doubt. Even if the caller's author _thinks_ he is
testing some condition and guaranteeing it to the callee, if the stack
is corrupt, any otherwise-correct test could easily give false results.

That's why, in general:

Preconditions are not ensured by the caller testing for them and
somehow deciding to avoid making the call. They are ensured by
reading the guarantees made to the caller by the other functions it
is calling, reading the requirements the caller makes on *its*
callers, and combining that information using logic to form a little
proof that the conditions hold.

It's fine for the caller to use asserts or whatever to check his own
logic and root out bugs from his code. However, it's impossible to
write a program that meets any useful specification when at any moment
the programmer's understanding of the program state could turn out to
be wrong, so those tests can at best be a debugging tool -- you'd
better write your program as though they're going to pass.

You have to decide on a baseline context inside of which your
guarantees live. "Obviously" if some code executed earlier induces
undefined behavior (e.g. by corrupting the stack), your guarantees are
just as meaningless as if radiation had inverted a few bits in memory.

>>>This would rule out stack integrity or other global conditions which
>>>the caller cannot guarantee.

Once again, I never said that stack corruption was a precondition
violation. You seem to be looking hard for ways to find that what
I've said is somehow inconsistent or incoherent. Poking holes in
arguments I never made seems sorta pointless.

It doesn't sound to me as though you're trying to understand what I'm
saying; rather, it seems much more as though you simply don't _like_
what I'm saying. If that's so, I'd like to stop trying to explain
myself now. If not, I apologize in advance for even asking.


>> If the caller is invoking the callee other than as some expression of
>> undefined behavior, it can. I'm assuming that even in Eiffel, the
>> moment stack integrity is violated you have undefined behavior. Once
>> you enter undefined behavior land, all bets are off and all actions
>> are part of that undefined behavior. Undefined behavior means "all
>> bets are off" and no guarantees (not even the ones you /think/ the
>> caller can ensure) are really valid.
>
> A violated precondition causes undefined behaviour. That doesn't
> mean undefined behaviour caused at a different level (like a broken
> compiler) can be regarded as a breach of a local contract.

No, it simply makes the local contract somewhat meaningless, unless
there is strong insulation between the levels (as with processes). A
broken compiler affects everything at a deep level, so there's no
insulation.

>>>Another conflicting use I have found is from P. J. Plauger's Editor's
>>>Forum in the June issue of C/C++ Users Journal where he writes about a
>>>new C library called "Safer C": "But any implementation of the
>>>function must test that its arguments meet all preconditions. The
>>>runtime equivalent of a diagnostic is to call the diagnostic handler.
>>>If the handler returns, the function then cauterizes any output
>>>buffers and returns an error code."
>>
>> I'm not sure that's a conflict either.
>
> To me, it is in glaring conflict with
>
>> The moment you say say, "I can do something reliably here if I detect
>> a violation," then the condition being violated is -- by definition --
>> no longer a precondition because you're defining what the behavior
>> should be when it happens.

If you are merely suggesting that Microsoft's "Safer C" specification
uses a different concept of the term "precondition," which allows a
documented response to violations, you'll get no argument from me on
that point. I never claimed that everyone in the world has the same
concept. Clearly you and I don't, and I daresay the fact that
somebody at Microsoft disagreed with me certainly doesn't prove
anything about the coherence of my arguments.

I am making only the following claims:

1. That any concept of "precondition violation" that allows the callee
to guarantee a particular response to a violation is very weak and
close to useless. It's logically indistinguishable from any other
documented behavior, aside from attaching some meaningless moral
judgement to the behavior ("violated preconditions are 'bad'; other
documented behaviors are 'good'"). A technical term is much more
powerful and useful when it distinguishes one thing from another.
Okay, you could use "precondition violation" as a shorthand for
"produces the following category of guaranteed behavior," as the
"safer C" spec appears to do. That brings me to...

2. The concept of precondition violation that allows the callee to
guarantee a particular response to a violation is usually bad for
callers. Because it's logically indistinguishable from any other
documented behavior, the caller almost invariably treats those
responses in the same way as "error" returns (e.g. resource
exhaustion, file locked, etc.) that can actually happen in correct
code. That results in either haphazard "recovery" code that doesn't
actually work consistently, or a huge overhead in code that tries to
accomodate these responses-to-one's-own-bugs correctly. Even if you
do the latter you still end up with the former, most of the time.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

First  |  Prev  |  Next  |  Last
Pages: 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Next: C++/CLI limitations?