8

In C and C++ assert is a very heavyweight routine, writing an error to stdout and terminating the program. In our application we have implemented a much more robust replacement for assert and given it its own macro. Every effort has been made to replace assert with our macro, however there are still many ways assert can be reintroduced (e.g., from internal third-party libraries, naïve injection, etc.)

Any suggestions on how we can reduce, limit or even eradicate uses of assert? The best answer will be one the compiler can catch for us so we don't have to babysit the code base as much as we do currently.

fbrereto
  • 35,429
  • 19
  • 126
  • 178
  • 4
    What exactly is wrong with `assert()` terminating the program? – Alex B Dec 04 '09 at 00:53
  • 3
    Well, for one, the program terminates leaving assert() only valid for OMG YOU BROKE THE UNIVERSE errors. Wouldn't it suck to be the user and have the program crash because your count of TPS report headers is off by one? Maybe you just want it to log the failed assertion so the user can continue what they're doing. Maybe you want to be send the assert failure rather than relying on the puzzled and annoyed user to know to do that. – Schwern Dec 04 '09 at 00:56
  • 14
    If your users are running debug builds, you have more serious problems. Assertions and logging are entirely different things, and should not be confused. – Tim Sylvester Dec 04 '09 at 00:59
  • 3
    I find it's useful to have two different types of assertions: unrecoverable and recoverable. An unrecoverable assertion is for something completely unexpected/broken/inconsistent about the program such that it is impossible to continue execution (or if you did, you would segfault immediately); you can and should terminate the program. For a recoverable assertion, you should display a dialog box, log an error, and continue on. – Adam Rosenfield Dec 04 '09 at 01:00
  • 14
    Sounds to me like you're using assert when you should be using exceptions. Assert is a debugging tool. It's not for program logic. That's why when you build in release mode, assert is compiled to nothing. – Cogwheel Dec 04 '09 at 01:09
  • 1
    @Schwern: If the error is recoverable, don't use an assert. You should show an error message, log it, or whatever suits your purposes instead on a case-by-case basis. @OP: Asserts quit for a reason: your data is not guaranteed to be in a valid state anymore. If you change that behavior, you risk creating huge messes: libraries do not expect the code to continue running after an assertion, and you'll probably face unexpected results. – danielkza Dec 04 '09 at 01:11
  • 3
    On careful reading of fbrereto's question and his comment to GMan's answer, it doesn't sound like they're necessarily misusing `assert()` - it sounds like they have their own assert facility that provides a better set of information than the compiler provided assert,and they'd like to more or less force developers (and 3rd party code) to use the custom assert facility. – Michael Burr Dec 04 '09 at 08:00
  • @Checkers: There is a lot wrong with assert. Besides the simple message it leaves, there is no useful debugging information. There could be a stack trace, a core dump and more. Often a crash will tell you more than a simple termintation with an assert(). – Gunther Piez Dec 04 '09 at 11:07
  • 2
    @drhirsch: A failed assertion kills the program with `abort(3)`, which raises the SIGART signal, which by default kills the program and dumps core; if it doesn't dump core, you may have set your core file limit to 0. Use `ulimit(1)` or `setrlimit(2)` to raise that limit. – Adam Rosenfield Dec 05 '09 at 03:00
  • "On careful reading of fbrereto's question" -- Actually, it's only grossly careless reading that could lead to missing this. The OP quite clearly referred to "third-party libraries", but nearly everyone but Michael ignored this. I'm in the same boat: a third party library is calling Assert where a failure return or exception would be more appropriate. – Jim Balter Jun 26 '13 at 17:42

10 Answers10

14

I'm not sure I really understand the problem, actually. Asserts are only expensive if they go off, which is fine anyway, since you're now in an exception situation.

assert is only enabled in debug builds, so use the release build of a third-party library. But really, asserts shouldn't be going off every moment.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 2
    They don't go off every moment, but given the size of our application we want to do more than simply disappear in the event an assertion fails. The ability to handle an assertion failure more gracefully provides a huge benefit to both QE and development. – fbrereto Dec 04 '09 at 01:11
  • 1
    @fbrereto: You should throw exceptions instead of calling assert. – Frank Dec 06 '09 at 04:06
  • additionally, if you change your production build to define `NDEBUG` then all the `assert()` pieces of your compiled code will be replaced with noop. – Trevor Boyd Smith May 10 '18 at 19:20
  • i supposed for third-party code that still is compiled with `assert()` you could change your linker to have a custom `assert` which does a noop... this might disable `asserts` in third-party code. – Trevor Boyd Smith May 10 '18 at 19:20
2

It would depend (at least in part) on what you're changing. Assuming that you don't mind it printing out its normal message, and mostly want to get rid of it calling abort(), you could consider leaving assert() alone, and instead defining your own version of abort().

In theory, doing that isn't portable -- but in reality, abort() is a fairly normal function in the standard library, and if you link your own instead, you get its behavior. Sometimes (especially some Microsoft linkers) you have to do a bit of work to get the linker to cooperate in replacing their abort() with yours, but it's rarely very difficult.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

It can be handy to improve upon the built-in assertion facility (to provide stack traces, core dumps, who knows). In that case, if you're having problems getting your developers to follow whatever standards you have (like "instead of assert() use SUPER_ASSERT()" or whatever), you can just put your own assert.h header in the include path ahead of the compiler's runtime directory of headers.

That'll pretty much guarantee that anyone using the standard assert() macro will get a compiler error or get your assertion functionality (depending on what you have your assert.h header do).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 1
    Ah, the directory path preemption is clever! – fbrereto Dec 04 '09 at 17:07
  • 1
    `SUPER_ASSERT` - hove you read John Robbins book? I picked up John's habit of asserting everything all the time - from state to parameters to return values. My code can't fart or sneeze without me knowing about it. It literally debugs itself (I spend less that 2 minutes under a debugger when tracing problems). I really pity people who don't use them. You can tell who they are - they are the ones concentrating intensely while starring at the debugger for hours throughout the day (or the ones adding all those useless `printf`s). – jww Jan 17 '14 at 10:55
  • @noloader: yes I have most (maybe all) of John Robbin's books. Definitely good stuff. – Michael Burr Jan 17 '14 at 11:02
  • @jww: What is the title of that book? – Peter Mortensen Dec 10 '17 at 16:17
  • @PeterMortensen - [Debugging Applications](https://www.amazon.com/dp/0735608865) by John Robbins. Robbins is one of the original Windows SoftICE developers way back when. Reading him is like reading Stevens on Unix. Don't worry about it being an old Windows book. Take away the concepts. The concepts apply to all programs and operating systems. – jww Dec 10 '17 at 17:19
2

I think your question is totally valid. If you have implemented your own error handling you may want to:

  1. Always trigger asserts even in release builds.
  2. Implement better error reporting in case an assert triggers. You may want to send error reports or write to log files.

That being said, I don't see any solution that always works.

  • If you are lucky, the third-party libraries use ASSERT macros that you can redefine yourself as long as the file defining this macro has some sort of #pragma once or #ifndef __HEADERFILE_H__ #define __HEADERFILE_H__ provision against multiple inclusion. Include the header file separately, redefine ASSERT and you're good.

  • If they directly include assert.h or cassert you can only patch the code I guess. Make minimal code changes, save the changes as patch files and when you update the library hope that the patches still work. Add the patches to version control.

If this doesn't work, rethink the question if you really need internal asserts in third-party libraries. Ship release builds only, this gets rid of the asserts, and add your ASSERTs to check for correctness inside your code. Check for validity of return values. If such an ASSERT is triggered, you can still dive into the third-party code to see what caused the problem.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sebastian
  • 4,802
  • 23
  • 48
2

I think the question is valid.

My very own assert expands to asm("int3") if triggered, which is equivalent to a breakpoint. I also found that vastly more useful for debugging than a simple termination.

I simply called it "ASSERT()" instead of the normal "assert()" and avoided the use of assert() at all.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gunther Piez
  • 29,760
  • 6
  • 71
  • 103
1

The most obvious approach would seem to be to give your own version of assert its own name, slightly different from assert(). Then you can search the text, look at linker messages, etc., for the literal string "_assert" and you know you have a problem when you see it.

In my own code, I always use Assert(), which expands to my own function that performs an assertion, or expands to ((void)0) for the release build. The compiler will turn the ((void)0) expression into nothing, but it still counts as an expression. Thus

Assert(3 == x);

will turn into

((void)0);

and the semicolon has a place to go.

By the way, I once worked on a GUI application where the assert was a special GUI modal popup dialog. You had three choices: Ignore, Ignore forever, or Break. Ignore would ignore the assert and keep running. Ignore forever would set a flag, and until you restarted the program in the debugger, that assert would not fire anymore. Break would allow the assert to break into the debugger.

I don't remember how they guaranteed that each assert had its own flag. Maybe when you wrote the Assert() call you had to specify a unique integer? It would be nice if it was more automatic than that. I'm pretty sure that the actual implmentation was a bit vector, and it would set the bit when you chose ignore forever.

steveha
  • 74,789
  • 21
  • 92
  • 117
  • Or, after writing "myAssert", instead of looking for "assert" everywhere, just "#define assert myAssert" and let the preprocessor do the work. I DO NOT ADVOCATE THIS, I'm just saying it can be done. – Beta Dec 04 '09 at 01:02
  • 1
    @Beta:In most cases, that #define would work -- but for assert it really won't. Since the meaning of `assert()` can change (even in a single source file) based on whether NDEBUG is defined, it can/will be #undef'd and #define'd again behind your back. – Jerry Coffin Dec 04 '09 at 01:20
  • @Jerry Coffin: I'm out of my depth here (and my impression is that there's no safety once macros are at war) but couldn't one undefine NDEBUG, either in a header or in the makefiles (or both)? Wouldn't that stop casual attempts to assert? – Beta Dec 04 '09 at 02:15
  • The whole purpose of NDEBUG is to allow you to compile some code one way for your "debug" build, and another way for your "release" build. For example, the debug build has asserts, and the release build has nothing at all for asserts. So, defining NDEBUG for all builds means you get only a release build and no debug build. We actually want asserts in our debug build. I use asserts *heavily* in my code; they cost exactly nothing in the release build, so they are a great tool. – steveha Dec 04 '09 at 02:54
1

assert() is usually #define'd to be ((void)0) for release code (#define NDEBUG), so there is no overhead at all.

When using a testing version, is the performance overhead hurting your ability for the testing to be realistic?

Cade Roux
  • 88,164
  • 40
  • 182
  • 265
  • A valid implementation of `assert()` *cannot* be defined to be empty for release code. For example, something like `assert(x), y=0;` is legitimate code, but would produce a syntax error if `assert()` became nothing in a release. A correct implementation would become something like `((void *)0)` when NDEBUG was defined. – Jerry Coffin Dec 04 '09 at 01:12
  • @Gman:Quite right -- my fingers "know" about casts to 'void *', but not 'void'... – Jerry Coffin Dec 04 '09 at 01:37
1

If source code is under your control:

#define NDEBUG
// Before
#include <assert.h>
// Or other header that includes assert.h

Or using precompiled header or compile options to define NDEBUG.

For third-part binaries, use the release version of them.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
OwnWaterloo
  • 1,963
  • 14
  • 11
1

You seem to be missing the fact that the third-party code is most likely written under the assumption of "standard" assert behavior. I.e. the code expects the program to terminate on failed assertion. The code that follows the assertion normally cannot and will not work correctly if the asserted condition is broken. In 99 cases out of 100 it will not work at all. In 99 cases out of 100 it will simply crash, i.e. the program will terminate anyway.

To believe that by overriding the assert behavior in third-party code you will somehow make the program live longer is naive at best.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    To assume the OP didn't already know that is naive at best. Did it ever cross your mind, that core dump, a stack trace, even a normal crash will provide more useful information for debugging? – Gunther Piez Dec 04 '09 at 12:16
  • @hirschhornsalz - I don't know many people who can read crash dumps effectively. Plus, its never appropriate for a third party library to crash your program. The library should either either process the request or they fail the call - crash (or shit all over themselves) is not an appropriate choice. – jww Jan 17 '14 at 11:06
0

Find assert in the library headers (assuming they're real files on your file system) and replace it with an invalid thing

// #define assert(condition) ... /* old definition */
#define assert(condition) ((condition) & "PLEASE DO NOT USE ASSERT" = 42)
pmg
  • 106,608
  • 13
  • 126
  • 198
  • 1
    And remember to do that again (and again ...) everytime you upgrade your compiler. – pmg Dec 04 '09 at 01:00
  • For "an invalid thing" I like to use: `#pragma error` The great thing is that this has almost the same effect whether the compiler supports it as a feature, or not. And it pretty clearly identifies that you intend an error there. – steveha Dec 04 '09 at 01:01
  • @steveha: I'm not sure you can use a #pragma error in a #define. I can't point to the location in the standard immediately, but I do not think it does what you think it does. – fbrereto Dec 04 '09 at 01:09
  • @fbrereto: you're right: C99, §6.10.3.4/3 says: "The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one, but all pragma unary operator expressions within it are then processed as specified in 6.10.9 below." A "pragma unary operator" is a _Pragma, not a #pragma. – Jerry Coffin Dec 04 '09 at 01:17
  • 1
    Sorry, I had to downvote this. Fiddling with system-wide library headers is a really, really bad idea, since it will potentially break every other program that you compile, or even the standard library itself. – Daniel Albuschat Jul 29 '14 at 05:15