tags:

views:

614

answers:

9

I am currently working on a large project, and I spend most of the time debugging. While debugging is a normal process, there are bugs, that are unstable, and these bugs are the greatest pain for the developer. The program does not work, well, sometimes... Sometimes it does, and there is nothing you can do about it.

What can be done about these bugs? Most common debugging tools (interactive debuggers, watches, log messages) may lead you nowhere, because the bug will disappear ... just to appear once again, later. That is why I am asking for some heuristics: what are the most common reasons for such bugs? What suspicious code should we investigate to locate such a bugs?

Let me start the list:

  1. using uninitialized variables. Common misprints like mMember = mMember;
  2. thread synchronization. Sometimes it can be a matter of luck;
  3. working with non-smart pointers, dereferencing invalid ones;

what else?

+1  A: 
  • Reading from uncached memory while a cache line is being written back over the memory (This is a right bastard to find).
  • Buffer overwrites
  • Stack overflows!

The only 3 i can think of at the mo ... may edit later :)

Goz
+1  A: 
  • buffer overflows
  • using pointers to deleted objects
  • returning invalid references or references to out of scope objects
  • unhandled exceptions
  • resource leaks (not only memory)
  • infinite recursion

  • dynamic libraries version mismatch

Cătălin Pitiș
I think both infinite recursion and unhandled exceptions don't count. They are usually reproduced (therefore, tracked) easily.
SadSido
+20  A: 

IME the underlying problem in many projects is that developers use low-level features of C++ like manual memory management, C-style string handling, etc. even though they are very rarely ever necessary (and then only well encapsulated in classes). This leads to memory corruption, invalid pointers, buffer overflows, resource leaks and whatnot. All the while nice and clean high-level constructs are available.

I was part of the team for a large (several MLoC) application for several years and the number of crashing bugs for different parts of the application nicely correlated to the programming style used within these parts. When asked why they wouldn't change their programming style some of the culprits answered that their style in general yields more performance. (Not only is this wrong, it's also a fact that customers rather have a more stable but slower program than a fast one that keeps crashing on them. Also, most of their code wasn't even required to be fast...)

As for multi-threading: I don't feel expert enough to offer solutions here, but I think Herb Sutter's Effective Concurrency columns are a very worthwhile read on the subject.

Edit to address the discussions in the comments:

I did not write that "C-style string handling is not more performant". (Certainly a lot of negation in this sentence, but since I feel misread, I try to be precise.) What I said is that high level constructs are not in general less performant: std::vector isn't in general slower than manually doing dynamically allocated C arrays, since it is a dynamically allocated C array. Of course, there are cases where something coded according to special requirements will perform better than any general solution -- but that doesn't necessarily mean you'll have to resort to manual memory management. This is why I wrote that, if such things are necessary, then only well-encapsulated in classes.

But what's even more important: in most code the difference doesn't matter. Whether a button depresses 0.01secs after someone clicked it or 0.05secs simply doesn't matter, so even a factor 5 speed gain is irrelevant in the button's code. Whether the code crashes, however, always matters.

To sum up my argument: First make it work correctly. This is best done using well-proven off-the-shelf building blocks. Then measure. Then improve performance where it matters, using well-proven off-the-shelf idioms.

sbi
I must concur that the problems I spend most time with are concurrency problems in other people's overly unstructured software.
xtofl
+1 for name dropping Sutter!
Chris Huang-Leaver
Small objection to your claim that C string style is not more performant...I got a 10x speedup changing code that stored a vector of strings to code that stored a vector of const char * begin,end pairs.
Zan Lynx
@Zan Lynx: Yes, and you can get a huge speedup by using string instead of char* in some cases too. Like, say, when you need the size of the string. Constant time .size() beats linear time strlen().Of course you can get a 10x speedup by fixing inefficient code. It is not char* that was magically efficient, it was the vector of strings that was a stupid way to structure the data.
jalf
It's the fact that string generally copies the buffer that makes it suck so much when interfacing with code that works with char*. A lot of C++ coders don't think about it so their socket server code or whatever turns into a memcpy benchmark.
Zan Lynx
If you're using nothing but high level constructs in C++ you're trying to use a scalpel to hammer in a nail. You can argue people are wrong for using the sharp end of the scalpel instead of hitting it with the flat side, but the fact is you want a hammer in the first place.
Rushyo
High level C++ structures (STL vectors, strings, etc.) are sometimes a pain to debug. You need a debugger that understand their internal structure to display a more sane version.
nimrodm
@nimrodm Why would you want to debug the standard library? Surely it's main advantage is that has already been debugged?
anon
@Neil: As I understand it, it's not about debugging `std::vector`, but about looking at the data of an instance of it while debugging your own code.
sbi
+1: Excellent answer. One general point to some of these comments: It's usually easier to take a simple working piece of slow code and optimise it than it is to take a fast piece of broken code and make it work.
Richard Corden
@Richard: I meant to put that bon mot into my answer, but forgot about it. Thanks for bringing it up it!
sbi
+5  A: 

I was actually going to post a question that asked exactly the opposite - do others find, as I do, that you spend almost no time using the debugger when working with C++? I honestly cannot remember the last time I used one - it must have been about six months ago.

Frankly, if you spend most of the time in the debugger, I think there is something very wrong with your basic coding practices.

anon
I cannot agree. Blessed are those programmers, who have to support only their own (perfectly designed) code. Most of the programmers have to support legacy (bad, undocumented) code as well.
SadSido
I concur - good code can be proven to be correct (or near enough) whereas most C and C++ code that is written is not done that rigorously. This means you end up having to deal with unexpected corner cases that make no sense, and then the need for a debugger comes in.
Kylotan
Most of the legacy code I've worked on in the past 15 years has been heavily multi-threaded middleware. In that environment, a debugger is worse than useless - it alters the timing of the code and actually hides bugs.
anon
This depends on your history. If you come from a background of C and try to inject that into C++ to compensate for lack of familiarity you are going to spend a large chunk of time looking for the gremlins. Developing good practices depends not only on your resolve but also on the baggage you carry with you.
ezpz
So you don't step through all new code in the debugger before checking it in? That's been standard practice everywhere I've worked.
Adrian McCarthy
@Adrian. Never. For most projects I've worked on it would have been impossible. It's one of these "best practices", promulgated by the otherwise sensible Steve Maguire, that I've never believed in.
anon
So, I guess you don't do a lot of low level programming. Try writing motion control applications without using a debugger. I hope that your bit-twiddling skills are perfect. -1
Ed Swangren
I code with a lot of pre and post condition asserts. Whenever one goes off I need the debugger to see what happened. This has never seemed like a bad practice to me.
Zan Lynx
Here! Here! to Adrian McCarthy. I really don't see how one can write code without a debugger if one steps through every line of code at least once.
pgast
@Ed I used to be a comms programmer, which involved a fair bit of low-level bit twidling and interrupt driven code. Hardly ever used a debugger then either.
anon
@Adrian: Step through all new code in the debugger? You're joking, right? I can't think of anything more pointless, time-consuming and symptomatic of an extreme lack of confidence than that.
Troubadour
@Troubadour: Ok the cola taste test. Try stepping through new code that you write over the next week or so and add a comment here with your results. I would be pretty surprised if you didn't find "something" at least 50% of your time (not necessarily a crashing bug).
Richard Corden
@Neil: Of course it's not necessary to use a debugger, but you must have some way of working out the program flow and inspecting variables - even if you just use "std::cout". What do you use to get this information? Of course maybe you're able to digest and execute the entire program in your head - but that really cannot be general advice for the rest of us mere mortals! :)
Richard Corden
+3  A: 

If you are really in a position where you already have bad code that breaks, the best plan is probably to throw as many tools at it as you can (OS/lib-level memory checking, automated testing, logging, core dumps, etc) to find the problem areas. Then rewrite the code to do something more deterministic. Most of the bugs come from people doing things that mostly work most of the time, but C++ offers stronger guarantees if you use the right tools and approaches.

Kylotan
+1  A: 

Not really a C++ issue but seen in a C/C++ project.

The trickiest issue I had to deal with was an initialization issue when starting up the OS on our platform that lead to unusual crashes. It took years before we found out what happened. Before that we ran the system overnight and if it didn't crash, then it was normally okay. Luckily, the OS isn't sold anymore.

stefaanv
+5  A: 

Race conditions.

These are one of the few things that still sends a shiver down my spine when it comes up in debugging (or in the issue tracker). Inherently horrible to debug, and extremely easy to create. The three most common causes of bugs in my C++ software have been race conditions, reliance on uninitialised memory, and reliance on static constructor order.

And if you don't know what race conditions are, chances are they're the cause of your instability ;)

Matthew Iselin
+1  A: 

Haven't seen this one mentioned yet:

  • Inheriting from a class that does not have a virtual destructor.
kenrogers
+1. Just grep the "undefined behaviour" from the C++ standard and receive the list of reasons for the floating bugs...
SadSido
A: 

addresses and memory used before allocation or after deallocation, segmentation faults, arrayoutofbounds, offset, threadlocks, unintelligible operator overloading, inline assembly, void exit and void in general where return values are desired complicates where math.h functions are worth a look since all math.h functions both have working arguments and return values compared to other library overly void, emptiness tests, nils, nulls and voids. 4 general conventions I recommend are return values, arguments, ternary choices and invertible changes. Faultprone to avoid are vectors (use arrays instead) void with empty arguments and in my subjective opinion I avoid the switch statement in favor of more intelligible or readable if...elseif or more abstract "is".

C++ also has rather lousy forward compatibility compared to scripts and interpreted, to try a decade old Java it still runs unchanged and safe in later vm.

LarsOn