开发者

Assisting in avoiding assert... always!

开发者 https://www.devze.com 2022-12-13 01:03 出处:网络
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

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 librarie开发者_Python百科s, 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.


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.


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).


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.


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.


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.


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.


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?


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.


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.


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)
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号