views:

756

answers:

16

There'a a handful of situations that the C++ standard attributes as undefined behavior. For example if I allocate with new[], then try to free with delete (not delete[]) that's undefined behavior - anything can happen - it might work, it might crash nastily, it might corrupt something silently and plant a timed problem.

It's so problematic to explain this anything can happen part to newbies. They start "proving" that "this works" (because it really works on the C++ implementation they use) and ask "what could possibly be wrong with this"? What concise explanation could I give that would motivate them to just not write such code?

+1  A: 

Let them try their way until their code will crash during test. Then the words won't be needed.

The thing is that newbies (we've all been there) have some amount of ego and self-confidence. It's okay. In fact, you couldn't be a programmer if you didn't. It's important to educate them but no less important to support them and don't cut their start in the journey by undermining their trust in themselves. Just be polite but prove your position with facts not with words. Only facts and evidence will work.

Developer Art
Yikes! Too much undefined behavior just happens to work for whatever they might want to do (they screw around with the code until "it works on my machine") - until they've moved on somewhere else and the tools are updated. Suddenly the bad aspects of the undefined behavior blow up in the face of whatever chump is still around maintaining the stuff.
Michael Burr
@Michael: I still think that "Developer Art" has a good point. There's no reason to freak out when discussing undefined behavior -- it's bad enough to be discouraged on the merits of the case. It's important to explain calmly and clearly why it's bad. The goal is to discourage the behavior, not the person.
JXG
@Michael: well, if this was a newbie in a professional setting, submitting code that I thought could blow up in future, then it wouldn't pass code review. If they continue to submit code that they know won't pass review, because they think they know better than their boss how to write C++, that's an issue for the gentle end of the disciplinary procedure. If they're actually a student, and not writing code that has to be maintained in future, then tasking them to port their crappy code to a compiler on which it breaks might be enough to illustrate the point - deal with their own fallout.
Steve Jessop
@Steve: not all code shops have code reviews (disregard the why of that) so in those cases it's important to have all committers write code in a reasonably safe manner. Also, I've had jobs with newbie coders nearby who coded away for a year or more on their own project, without code review (with approval from above). Their ways are set then, they tend to do quirky things and it's extremely hard to change their attitude and basically, whatever talent they had is mostly wasted. (They can still code, and make stuff work, but they will never be true professionals...)
Marcus Lindblom
True. Even if a company doesn't review all code, or regularly review all coders, it should certainly review code from new coders, and keep reviewing it until they get it more or less right (by whatever standards the company holds - I'm assuming the company's guidelines support sharptooth's expectation that "works for me" isn't good enough). New hires are on probation, surely? Code review can fall by the wayside, but you need to have some grounds for confidence in the coder in the first place, so if their code continually introduces risks for the future, you want to know about it.
Steve Jessop
+23  A: 

Two possibilities come to my mind:

  1. You could ask them "just because you can drive on the motorway the opposite direction at midnight and survive, would you do it regularly?"

  2. The more involved solution might be to set up a different compiler / run environment to show them how it fails spectacularly under different circumstances.

Péter Török
The difficulty with solution 2 is that you may find that none of the compilers available actually fails spectacularly, but the behaviour is still not defined and therefore cannot be depended upon. Too much code is written with "I cannot find somewhere that it breaks", and while that may currently be the case, something could change (platform, compiler, ...) that will make it break in the future - and there is no bug in the system because the behaviour is undefined (so anything is correct). Also search for 'nasal demons' in the comp.std.c news group.
Jonathan Leffler
@Jonathan-- see my answer!
JXG
+4  A: 

"Congratulations, you've defined the behavior that compiler has for that operation. I'll expect the report on the behavior that the other 200 compilers that exist in the world exhibit to be on my desk by 10 AM tomorrow. Don't disappoint me now, your future looks promising!"

Ignacio Vazquez-Abrams
The bigger problem is that the same compiler might exhibit a different behavior when compiled with slightly different source or options (or even at a different time of day or on a different machine). So for undefined behavior you can't even really say 'that's what that particular compiler does' - that's what 'implementation specified' behavior is for.
Michael Burr
+2  A: 

Quietly override new, new[], delete and delete[] and see how long it takes him to notice ;)

Failing that ... just tell him he is wrong and point him towards the C++ spec. Oh yeah .. and next time be more careful when employing people to make sure you avoid a-holes!

Goz
A: 

Turn on malloc_debug and delete an array of objects with destructors. freeing a pointer inside the block should fail. Call them all together and demonstrate this.

You'll need to think of other examples to build your credibility until they understand that they are newbies and there's a lot to know about C++.

Potatoswatter
A: 

Tell them about standards and how tools are developed to comply with the standards. Anything outside the standard might or might not work, which is UB.

piotr
+23  A: 

Undefined means explicitly unreliable. Software should be reliable. You shouldn't have to say much else.

A frozen pond is a good example of an undefined walking surface. Just because you make it across once doesn't mean you should add the shortcut to your paper route, especially if you're planning for the four seasons.

Alex Neth
+1 for imagination, sometimes a picture is worth a thousand words.
Matthieu M.
+1  A: 

One would be...

"This" usage is not part of the language. If we would say that in this case the compiler must generate code that crashes, then it would be a feature, some kind of requirement for the compiler's manufacturer. The writers of the standard did not wanted to give unnecessary work on "features" that are not supported. They decided not to make any behavioral requirements in such cases.

Notinlist
+1  A: 

Just show them Valgrind.

Piotr Justyna
+2  A: 

I like this quote:

Undefined behavior: it may corrupt your files, format your disk or send hate mail to your boss.

I don't know who to attribute this to (maybe it's from Effective C++)?

Manuel
but if their argument is "look, I just compiled and ran the program and it worked", why would your quote change their minds?
jalf
@jalf - well you simply have to replace "your" with "our client's" in the quote
Manuel
But again, they just *showed* that it worked. Who cares what some dusty old quote says? Why shouldn't they trust that if it worked when they ran the program, it's *always* going to work?
jalf
They didn't show that it works in the computers of all potential clients, nor that it works if compiled by another compiler or even by a previous/future version of the same compiler. If they can prove *that*, then I'd guess I'd just rest my case
Manuel
+11  A: 

I'd explain that if they didn't write the code correctly, their next performance review would not be a happy one. That's sufficient "motivation" for most people.

anon
+1 heheh, sometimes you just have to pull rank :)
Paolo
+1  A: 

John Woods:

In short, you can't use sizeof() on a structure whose elements haven't been defined, and if you do, demons may fly out of your nose.

"Demons may fly out of your nose" simply must be part of the vocabulary of every programmer.

More to the point, talk about portability. Explain how programs frequently have to be ported to different OSes, let alone different compilers. In the real world, the ports are usually done by people other than the original programmers. Some of these ports are even to embedded devices, where there can be enormous costs of discovering that the compiler decided differently from your assumption.

JXG
+1  A: 

Compile and run this program:

#include <iostream>

class A {
    public:
            A() { std::cout << "hi" << std::endl; }
            ~A() { std::cout << "bye" << std::endl; }
};

int main() {
    A* a1 = new A[10];
    delete a1;

    A* a2 = new A[10];
    delete[] a2;
}

At least when using GCC, it shows that the destructor only gets called for one of the elements when doing single delete.

About single delete on POD arrays. Point them to a C++ FAQ or have them run their code through cppcheck.

kotlinski
+2  A: 

C++ is not really a language for dilletantes, and simply listing out some rules and making them obey without question will make for some terrible programmers; most of the stupidest things I see people say are probably related to this kind of blind rules following/lawyering.

On the other hand if they know the destructors won't get called, and possibly some other problems, then they will take care to avoid it. And more importantly, have some chance to debug it if they ever do it by accident, and also to have some chance to realize how dangerous many of the features of C++ can be.

Since there's many things to worry about, no single course or book is ever going to make someone master C++ or probably even become that good with it.

Charles Eli Cheese
+7  A: 

Simply quote from the standard. If they can't accept that, they aren't C++ programmers. Would Christians deny the bible? ;-)

1.9 Program execution

  1. The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. [...]

  2. Certain aspects and operations of the abstract machine are described in this International Standard as implementation-defined (for example, sizeof(int)). These constitute the parameters of the abstract machine. Each implementation shall include documentation describing its characteristics and behavior in these respects. [...]

  3. Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function). Where possible, this International Standard defines a set of allowable behaviors. These define the nondeterministic aspects of the abstract machine. [...]

  4. Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer). [ Note: this International Standard imposes no requirements on the behavior of programs that contain undefined behavior. —end note ]

You can't get any clearer than that.

FredOverflow
*"Would Christians deny the bible?"* -- Heh, you might be surprised...
Michael Myers
A: 

Just because their program appears to work is a guarantee of nothing; the compiler could generate code that happens to work (how do you even define "work" when the correct behavior is undefined?) on weekdays but formats your disk on weekends. Did they read the source code to their compiler? Examine their disassembled output?

Or remind them just because it happens to "work" today is no guarantee of it working when you upgrade your compiler version. Tell them to have fun finding whatever subtle bugs creep up from that.

And really, why not? They should be providing a justifiable argument to use undefined behavior, not the other way around. What reason is there to use delete instead of delete[] other than laziness? (Okay, there's std::auto_ptr. But if you're using std::auto_ptr with a new[]-allocated array, you probably ought to be using a std::vector anyway.)

jamesdlin