5

I want to create my own version of assert in which it does some log prints in case assert was called in NDEBUG mode.

I tried to do the LD_PRELOAD trick and redefine the assert macro but it seems to ignore the macro definition completely and overriding __assert_fail is irrelevant since it isn't called in case of NDEBUG.

How can I override the libc assert macro?

I do not want to create a different function since assert is already used heavily in the project.

MasterAM
  • 16,283
  • 6
  • 45
  • 66
itayb
  • 139
  • 3
  • 10
  • 2
    Assert is a macro, so the LD_PRELOAD trick isn't applicable. You will need to undefine the standard macro (or not include the header file that defines it), and then define your own assert macro *before* including any of the other headers that use it. If the code is already compiled into a library, it is too late. – Cody Gray - on strike Feb 23 '16 at 13:35
  • 3
    `I do not want to create a different function since assert is already used heavily in the project.` It's not a problem if you use a text editor from 1980th or more recent. Refactor->rename or grep and diff/VCS might save you a lot of troubles and will allow you to avoid inventing ugly hacks. – Ivan Aksamentov - Drop Feb 24 '16 at 11:12

5 Answers5

4

Had the same problem using gcc on Cygwin/Windows and on Linux.

My solution is to overwrite the (weak) definition of the actual assertion failed handling function. Here is the code:

/*!
 * Overwrite the standard (weak) definition of the assert failed handling function.
 *
 * These functions are called by the assert() macro and are named differently and
 * have different signatures on different systems.
 * - On Cygwin/Windows its   __assert_func()
 * - On Linux its            __assert_fail()
 *
 * - Output format is changed to reflect the gcc error message style
 *
 * @param filename    - the filename where the error happened
 * @param line        - the line number where the error happened
 * @param assert_func - the function name where the error happened
 * @param expr        - the expression that triggered the failed assert
 */
#if defined( __CYGWIN__ )

void __assert_func( const char *filename, int line, const char *assert_func, const char *expr )

#elif defined( __linux__ )

void __assert_fail ( const char* expr, const char *filename, unsigned int line, const char *assert_func )

#else
# error "Unknown OS! Don't know how to overwrite the assert failed handling function. Follow assert() and adjust!"
#endif
{
    // gcc error message style output format:
    fprintf( stdout, "%s:%d:4: error: assertion \"%s\" failed in function %s\n",
             filename, line, expr, assert_func );

    abort();
}
docmarvin
  • 79
  • 4
  • When `NDEBUG` is defined, `assert` will expand to nothing and underlying function will not be called. – yugr May 03 '18 at 09:00
  • 1
    Yes. Thats the expected behaviour. So the solution works consistent to the "standard" behaviour. – docmarvin May 03 '18 at 10:12
  • 1
    But OP asked about overloading assert in `NDEBUG` case: "I want to create my own version of assert ... in case assert was called in NDEBUG mode". – yugr May 03 '18 at 15:11
  • Aaah, you're right. I didn't understand the _in NDEBUG mode_ from the original question. So if NDEBUG is defined then indeed this solution will not help at all. It is only usefull in case you need to customize the format or the target (file/socket/...) of the assertion failed messages. Sorry for my misunderstanding. – docmarvin May 03 '18 at 21:34
  • Sadly, it does not work under mingw. – user362515 Dec 06 '22 at 14:03
2

The C99 rationale provides a sample on how to redefine the assert in a good way on page 113:

#undef assert
#ifdef NDEBUG
#define assert(ignore) ((void)0)
#else
extern void __gripe(char *_Expr, char *_File, int _Line, const char *_Func);
#define assert(expr) \
((expr) ? (void)0 :\
 __gripe(#expr, _ _FILE_ _,_ _LINE_ _,_ _func_ _))
#endif 

I'd include assert.h right before this code to make sure assert.h is used. Also notice that it calls a function that would do reporting logic, so that your code would be smaller.

Community
  • 1
  • 1
0kcats
  • 672
  • 5
  • 16
  • Notice, that if assert.h or cassert is included after this, it's going to redefine assert to the standard definition (assert.h does not have include guard). – 0kcats Oct 29 '16 at 04:30
  • Why not just add code to local `assert.h` and add it's path to `CPPFLAGS`? It would then override system `assert.h`. – yugr May 03 '18 at 09:03
1

It is a pretty simple thing to do, since assert is a macro. Given that you have this code:

#define NDEBUG
#include <assert.h>

int main( void )
{
  assert(0);

  return 0;
}

Then just do:

#ifdef NDEBUG
#undef assert
#define assert(x) if(!(x)){printf("hello world!");} // whatever code you want here
#endif

Note that this has to be done after #include <assert.h> though.

So if you want to stick your own definition into a common header file, and then use that header file to modify existing code, then your header file have to be included after assert.h.

my_assert.h

#include <assert.h>
#include <stdio.h>

#ifdef NDEBUG
#undef assert
#define assert(x) if(!(x)){printf("hello world!");}
#endif

main.c

#define NDEBUG    
#include <assert.h>
#include "my_assert.h"

int main( void )
{
  assert(0); // prints "hello world!"
  assert(1); // does nothing

  return 0;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    As a side note, I'm rather sure that, formally and pedantically, `#undef` of a library macro is UB. In this specific case, there is no reason why it shouldn't work though. – Lundin Feb 23 '16 at 16:20
  • Slight nit pick but `#define assert(x) do{ if(!(x)){printf("hello world!"); }} while(0)` is better because then semicolons work as expected – JeremyP Feb 23 '16 at 16:23
  • @JeremyP No, that's a bad idea. That way you will _not_ get compiler errors when someone uses your code like `if(x) assert(something); else` with no braces. You should never write if statements without braces (ask Apple if doing so was a good idea). If your macro forces the user to write safer C, your macro is doing good things. "do-while-zero" macros are as obsolete as the "yoda conditions" and other such nonsense tricks from the 1980s. – Lundin Feb 23 '16 at 16:33
  • People will write code with no braces for the if part whether you like it or not, it is not your job to police other people's coding style. And in fact, such code may exist in the questioner's code base. You absolutely must use the `do { ... } while(0)` to make assert behave syntactically as expected in all situations. – JeremyP Feb 23 '16 at 16:44
  • @JeremyP Rather, it is not my job as professional C programmers to adapt my code to suit programs written by amateurs. I'll have to assume that my code will be used by other professionals. Skipping braces is not just some style issue, it is a blatant safety hazard, as been proven empirically over and over again. To the point where lack of braces could be regarded as a bug. You'll find that coding standards like MISRA and CERT, that are explicitly _not_ style guides and only concerned with safety/security, still enforce the use of braces. – Lundin Feb 24 '16 at 07:36
  • @JeremyP That argument aside, it is not the purpose of SO to show beginners how to write backwards-compatible code for some imaginary scenario when they in some future will maintain a crappy legacy code base. The purpose of SO is rather to show them how to write high quality code. – Lundin Feb 24 '16 at 07:38
  • 1
    This solution requires replacing of `#include ` to `#include #include "my_assert.h"` in every source file where `assert` is used. This way, simple textual replacement of all instances of `assert` to a custom macro would probably be easier and more robust. – Ivan Aksamentov - Drop Feb 24 '16 at 11:24
  • 1
    @LundinIt is equally not the purpose of SO to police coding style. `asset()` looks like a function, it should behave like a function syntactically and not cause unexpected syntax errors in otherwise legal situations. Your version of `assert()` is therefore not high quality code or, at least, not as high quality as it could be. – JeremyP Feb 24 '16 at 13:39
  • @JeremyP _It is not coding style_. Realize! It's as if I told you not to use `i=i++ + ++i;` and then you tell me that I shouldn't mind what coding style you use. Not using braces is blatantly dangerous practice, period. It's a ticking bomb. It is not a question _if_ it will lead to bugs, it is a question of _when_ the bug will strike. Can you, or anyone else honestly say that they have not written a bug which wasn't caused by lack of braces? I wrote several such bugs myself, back in the days when I was naive and didn't use braces either. – Lundin Feb 24 '16 at 14:07
  • It is coding style. And `i=i++ + ++i;` is not a matter of coding style because it is undefined behaviour since it modifies one variable twice between two sequence points (you might want to have a look at the C standard if you don't know what a sequence point is). Not using braces for single statement `if`s might be dangerous but defining function like macros the wrong way is not the way to flag them. If you want to make sure you don't use them, use a linter. Defining a broken `assert()` macro as you have won't even catch the majority of uses of one line if statements. – JeremyP Feb 26 '16 at 10:13
  • "Note that this has to be done after #include though" - or OP can just put his new assert a custom assert.h macro and ensure that it comes earlier in search path. – yugr Nov 14 '16 at 10:37
  • Why not just add code to local `assert.h` and add it's path to `CPPFLAGS`? It would then override system `assert.h`. – yugr May 03 '18 at 09:03
1

An attempt to try to override the assert() macro in a large codebase can be difficult. For example, suppose you have code like:

#include <assert.h>
#include "my_assert.h"
#include "foo.h" // directly or indirectly includes <assert.h>

After this, any use of assert() will again use the system assert() macro and not the one that you have defined in "my_assert.h" (this is apparently part of the C design of the assert macro).

There are ways to avoid this, but you have to use nasty tricks like putting your own assert.h header in the include path before the system assert.h, which is somewhat error prone and non-portable.

I'd recommend using a different named macro than assert, and use regex tricks or a clang-rewriter to rename the various assert macros in your codebase to an assert macro that you can control. Example:

perl -p -i -e 's/\bassert\b *\( */my_assert( /;' `cat list_of_filenames`

(then adding "my_assert.h" or something like it to each of the files modified)

Peeter Joot
  • 7,848
  • 7
  • 48
  • 82
  • Why not just add code to local `assert.h` and add it's path to `CPPFLAGS`? It would then override system `assert.h`. – yugr May 03 '18 at 09:03
  • That requires confidence and testing, to ensure that all the compilers in use (or that may be used) will allow an override of a system header using -I. When I wrote this answer, I worked on a product built on 11 platform variations with a number of compilers. I wouldn't have had the confidence to try that with our product. – Peeter Joot May 03 '18 at 15:46
  • Well, I haven't seen a single compiler which wouldn't allow override of system headers via `-I` (including Visual Studio) and I don't believe there are reasons why such compiler would exist... – yugr May 03 '18 at 17:52
-1

You can check if NDEBUG is defined and if it is then print whatever logs you want to print.

yugr
  • 19,769
  • 3
  • 51
  • 96
pasha
  • 2,035
  • 20
  • 34
  • @StoryTeller ...I agree with you but for this question I felt providing the idea was enough. – pasha Feb 24 '16 at 11:38