Why does assert simply terminate a program compile

2020-02-17 08:07发布

问题:

I'm debugging a heavily assert()'ed iPhone app (Xcode, Objective-C++, and device simulator). In some cases, the assert failure would just terminate the app, instead of breaking into the debugger as I'd expect.

I made a workaround by implementing my own kinda-assert to the effect of:

#define AssertLite(b) if(!(b)) {asm {int 3}}

(fluff omitted), but I wonder if anyone ever encountered this. I could not determine a pattern as to when does it break and when does it terminate. The code is not threaded; all it does is done in event handlers.

Why does this happen and how do I make vanilla assert() behave like a conditional breakpoint it should be?

回答1:

First off, since you are working on an iPhone app, you should probably use NSAssert() instead of the vanilla BSD assert function.

e.g. NSAssert(the_object, @"NIL object encountered");

The NSAssert macro will throw an Objective-C exception (NSInternalInconsistencyException) if the assertion fails.

Since your goal is to break on the exception, the next step is to make the Xcode debugger break on Objective-C exceptions. This is probably a good thing to do anyway.

In the Breakpoints window (Run->Show->Breakpoints menu item), click where it says "Double-Click for Symbol" to enter the symbol -[NSException raise]

The last thing to be careful off is that NSAsserts do not compile out in a release build. That means that you have to either be prepared to handle the exception in your application, or you need to create your own macro that does compile out in release builds.

Here's the macro I use to compile out assertions in runtime code (note that I then use HMAssert in my code instead of NSAssert):

#ifdef DEBUG
#   define HMAssert(A,B) NSAssert(A,B)
#else
#   define HMAssert(A,B)
#endif

This requires a DEBUG preprocessor macro to be defined. Here's how to set that up:

  1. Right-click on your project in Xcode. That will be the top item in the left panel where your projects files are listed
  2. Select "Get Info" from the context menu that pops up.
  3. Go to the "Build" tab.
  4. Make sure the "Configuration" is set to "Debug".
  5. Type DEBUG into the field next to "Preprocessor Macros" under "GCC 4.2 - Preprocessing".


回答2:

First of all, if you "Add Exception Breakpoint..." in the Breakpoint Navigator (⌘6), the debugger will stop on NSAssert's failures, allowing you to look at the stack and understand what went wrong.

You should use the standard NSAssert. If you use it correctly, there is not a lot that you need to manually create -- everything Mike mention is similar to the default NSAssert implementation.

You should run you release configuration with NS_BLOCK_ASSERTIONS set in your precompiled headers (follow Mike's steps), to disable assertions. If you need more info on why to do so, check out: http://myok12.wordpress.com/2010/10/10/to-use-or-not-to-use-assertions/



回答3:

In Xcode 4 and new iOS, NSAssert may actually take a variable list of parameters. This may be useful to log some values together with the assert. The compiling-out assert (see answer by Mike above) could be defined like this:

#ifdef DEBUG
#   define DAssert(A, B, ...) NSAssert(A, B, ##__VA_ARGS__);
#else
#   define DAssert(...);
#endif

Also, there is no longer Run → Show → Breakpoints menu item. See this post to set up Xcode 4 to break on an assert as defined above.



回答4:

One time I saw a different behavior from the assert() calls once. It was caused by the compiler picking up different macro definitions at different portions of the build process.

Once the include paths were straightened out, they all worked the same.