views:

538

answers:

9

What I don't understand about C/C++ is:

Yes, everyone uses it to get blazingly fast executables, so they compile with optimization turned on.

But for compilation with debug information turned on, we don't care about speed. So why not include more information in that compile mode, for example detect some segfaults before they happen? Effectively, insert an assert(ptr != NULL) before every access to a pointer ptr. Why can't the compiler do that? Again, that should be off by default, but there should be such a possibility, I think.

EDIT: Some people said that the detection I suggested doesn't make sense or doesn't do anything that the report of segmentation fault wouldn't already do. But what I have in mind is just a more graceful and informative abort, which prints the file name and line number of the offending code, just like an assert() would do.

+8  A: 

What should the program do in that case? If it informs the user of a bug, then that's what the segfault does.

If it's supposed to keep going and avoid the bug, how does it know what to do?

Not to mention that if it did somehow magically know how to continue properly, then you have a bug in your release build (debug builds are intended to help you identify and fix bugs - not hide them).


In response to the additional information added to the question (I guess I misunderstood your intent):

what I have in mind is just a more graceful and informative abort, which prints the file name and line number of the offending code, just like an assert() would do.

This is something the compiler could do - as you say, the compiler would essentially be automatically inserting an assert() anywhere a pointer was dereferenced. This might add pretty significantly to the size of a debug build, but it would probably still be acceptable for many (or most) purposes. I think this would be a reasonable option for a compiler to provide.

I'm not sure what compiler vendors would say... Maybe post a request on Microsoft's Connect site for the VC++ product and see what they say.

Michael Burr
+1  A: 

I agree with Michael Burr that this doesn't really do or help anything.

Furthermore, this still wouldn't work for dangling pointers which tend to be far more insidious and difficult to track down than null pointers.

At least with null pointers it's simple enough to ensure they are valid before you deref them.

+8  A: 

There are a few major problems with your suggestion:

What conditions do you want the compiler to detect? On Linux/x86, unaligned access can cause SIGBUS and stack overflow can cause SIGSEGV, but in both cases it technically is possible to write the application to detect those conditions and fail "gracefully". NULL pointer checks can be detected, but the most insidious bugs are due to invalid pointer access, rather than NULL pointers.

The C and C++ programming languages provide enough flexibility so it is impossible for a runtime to determine with 100% success if a given random address is a valid pointer of an arbitrary type.

What would you like the runtime environment to do when it detects this situation? It can't correct the behavior (unless you believe in magic). It can only continue executing or exit. But wait a minute... that's what already happens when a signal is delivered! The program exits, a core dump is generated, and that core dump can be used by application developers to determine the state of the program when it crashed.

What you're advocating actually sounds like you want to run your application in a debugger (gdb) or through some form of virtualization (valgrind). This is already possible, but it makes no sense to do it by default, because it provides no benefit to non-developers.

Update to respond to comments:

There's no reason to modify the compilation process for debug versions. If you need a "gentle" debug version of the application, you should run it inside of a debugger. It's very easy to wrap your executable in a script that does this for you transparently.

Tom
These are good comments. Often it's not a NULL pointer but some other invalid write or read.
Non-developers wouldn't run the debug version anyway. So, for the debug version, I just wish it were a bit nicer; it could also automatically show the stack trace on abort etc. I know that's possible with some additional code, so the compiler could as well do that automatically (compiler option).
A: 

So you're saying that before the system throws an error, it should throw an error to.... warn you of the impending error?

What'd be the point? When I get a segfault, I know it means I got a segfault. I don't need a separate message first saying "you will now get a segfault".

Am I completely missing the point here? :p

Edit: I see what you mean in your edit, but it's not easy to implement. The problem is that it's not the compiler or the language or the runtime that decides what should happen if you access a bad pointer. The language officially makes no promises or guarantees about this. Instead, the OS fires off an error, without knowing that this is a debug executable, without knowing which line number triggered the problem, or anything else. The only thing this error says is "you tried to access address X, and I can't allow that. Die". What should the compiler do with this?

So who should generate this helpful error message? And how? The compiler could do it, sure, but wrapping every single pointer access in error handling, to ensure that if a segfault/access violation occurs, we catch it, and trigger an assert instead. The problem is, this would be ridiculously slow. Not just "too slow for release", but "too slow to be usable". It also assumes that the compiler has access to all code you call into. What if you call a function in a third-party library? Pointer accesses inside that can't be wrapped in error handling code, because the compiler doesn't generate code for that library.

The OS could do it, assuming it was willing/able to load the relevant symbol files, somehow detect whether or not you're running a debug executable and so on... Just so it can print out a line number. Talk about overengineering. This is hardly the OS's job.

And finally, what would you gain by doing this? Why not simply fire up your debugger? It automatically breaks when something like this happens, giving you precise line number and everything else.

It could be done, but it'd be awfully complicated, and involve both the compiler and the OS, and the benefit would be extremely small. You'd get a popup box telling you information that your debugger is already able to tell you. And with that information, you'd then... fire up your debugger anyway to find out what went wrong.

jalf
A: 

There is already the equivalent of a assert(prt != NULL) as a part of the OS. That's why you get a segfault instead of just overwriting important important data at the 0 address and then really messing up the system.

DasBoot
+1  A: 

I think the original poster wants the app to stop in the debugger. You would have access to all of the stack variables and stack so you would have a chance to figure out why your program is in this state.

If you are developing in C/C++, a debugging memory manager can save you a ton of time. Buffer overruns, accessing deleted memory, memory leaks, and so on are pretty easy to find and fix. There are several on the market or you can spend 2 or 3 days to write your own and get 90% of the needed functionality. If you are writing apps without them, you are making your job much more difficult than it needs to be.

hacken
A: 

Given that you have a symbol file for your executable, it is possible to map the location of the crash to a line number. The debugger does this for you if you are running it in the debugger, as others have mentioned. Visual C++ even offers "just-in-time" debugging where, when the program crashes, you can attach a debugger to the crashed process to see where the problem is.

However, if you want to have this functionality on machines where Visual C++ is not installed, it is still possible to do it with some coding. You can set up an exception handler using SetUnhandledExceptionFilter that will be called when your program crashes. In the handler, you can look at the exception record and use SymGetLineFromAddr64 to determine what source line that was executing. There are many functions in the "debug help" library that lets you extract all sorts of information. See the articles on MSDN, and also the articles on www.debuginfo.com.

flodin
+1  A: 

There's one more reason a simple assert(ptr != NULL) won't work before dereferencing a pointer won't work: Not every invalid pointer (even those that began life as NULL) is in fact equal to 0.

First consider the case where you have a struct with several members:

struct mystruct {
    int first;
    int second;
    int third;
    int fourth;
};

If you have a pointer ptr to mystruct and you try to access ptr->second, the compiler is going to generate code that ads 4 (assuming 32-bit integers) to ptr and access that memory location. If ptr is 0, the actual memory location accessed will be 4. That's still invalid but wouldn't be caught by a simple assertion. (The compiler could reasonably be expected to check the address of ptr before adding 4, in which case the assertion would catch it.)

Second, consider the case where you have an array of struct mystruct and you pass an arbitrary element to another function. If you try to access the second element of the array, it will begin at 16 bytes beyond the first pointer. There's no way the compiler could reasonably be expected to do what you want reliably in all cases, without catching legitimate pointer arithmetic.

What you really want to do is use the operating system and hardware to catch invalid and unaligned memory access and kill your application, then figure out how to get the debugging information you need. The easiest way is simply to run inside a debugger. If you're using gcc on Linux, see how to generate a stacktace when my C++ app crashes. I assume there are similar ways to do the same thing with other compilers.

Commodore Jaeger
A: 

Good idea, but only in one particular case. That is before you're dereferencing a funciton pointer. The reason is that the debugger will always kick in, but after dereferencing a null function pointer the stack is shot. Hence, you have problems finding the offending code. If the caller checked before calling, the debugger would be able to give you a full stack.

A more generic check would be to see if the pointer points to memory which is executable. NULL isn't, but many Operating Systems also can use CPU features to make specific memory segments non-executable.

MSalters