How to override assert macro in C?
Asked Answered
L

5

6

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.

Libenson answered 23/2, 2016 at 13:15 Comment(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.Ascogonium
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.Earring
B
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();
}
Benedictine answered 3/5, 2018 at 8:22 Comment(5)
When NDEBUG is defined, assert will expand to nothing and underlying function will not be called.Homoeroticism
Yes. Thats the expected behaviour. So the solution works consistent to the "standard" behaviour.Benedictine
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".Homoeroticism
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.Benedictine
Sadly, it does not work under mingw.Elsaelsbeth
P
3

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(const char *_Expr, const 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.

Pratt answered 28/10, 2016 at 15:52 Comment(2)
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).Pratt
Why not just add code to local assert.h and add it's path to CPPFLAGS? It would then override system assert.h.Homoeroticism
F
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;
}
Feathery answered 23/2, 2016 at 16:15 Comment(12)
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.Feathery
Slight nit pick but #define assert(x) do{ if(!(x)){printf("hello world!"); }} while(0) is better because then semicolons work as expectedFic
@Fic 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.Feathery
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.Fic
@Fic 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.Feathery
@Fic 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.Feathery
This solution requires replacing of #include <assert.h> to #include <assert.h> #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.Earring
@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.Fic
@Fic 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.Feathery
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 ifs 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.Fic
"Note that this has to be done after #include <assert.h> though" - or OP can just put his new assert a custom assert.h macro and ensure that it comes earlier in search path.Homoeroticism
Why not just add code to local assert.h and add it's path to CPPFLAGS? It would then override system assert.h.Homoeroticism
J
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)

Justen answered 28/10, 2016 at 16:17 Comment(3)
Why not just add code to local assert.h and add it's path to CPPFLAGS? It would then override system assert.h.Homoeroticism
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.Justen
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...Homoeroticism
L
-1

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

Lonergan answered 23/2, 2016 at 14:59 Comment(1)
@StoryTeller ...I agree with you but for this question I felt providing the idea was enough.Lonergan

© 2022 - 2024 — McMap. All rights reserved.