如何在C中覆盖断言宏?

时间:2016-02-23 13:15:52

标签: c assert ld-preload ndebug

我想创建自己的assert版本,在NDEBUG模式下调用assert时会执行一些日志打印。

我尝试执行LD_PRELOAD技巧并重新定义断言宏但它似乎完全忽略了宏定义,并且覆盖__assert_fail是无关紧要的,因为在{{1}的情况下不会调用它}。

如何覆盖NDEBUG libc宏?

我不想创建不同的函数,因为assert已在项目中大量使用。

5 个答案:

答案 0 :(得分:2)

尝试覆盖大型代码库中的assert()宏可能很困难。例如,假设您有以下代码:

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

在此之后,任何对assert()的使用都将再次使用系统assert()宏,而不是你在“my_assert.h”中定义的那个(这显然是断言宏的C设计的一部分)。

有一些方法可以避免这种情况,但你必须使用令人讨厌的技巧,例如在系统assert.h之前将自己的assert.h头放在include路径中,这有点容易出错且不可移植。

我建议使用与assert不同的命名宏,并使用正则表达式技巧或clang-rewriter将代码库中的各种断言宏重命名为可以控制的断言宏。例如:

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

(然后将“my_assert.h”或类似内容添加到每个修改过的文件中)

答案 1 :(得分:1)

这是一件非常简单的事情,因为assert是一个宏。鉴于你有这个代码:

#define NDEBUG
#include <assert.h>

int main( void )
{
  assert(0);

  return 0;
}

然后就这样做:

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

请注意,这必须在 #include <assert.h>之后完成。

因此,如果您想将自己的定义粘贴到公共头文件中,然后使用该头文件修改现有代码,则必须在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;
}

答案 2 :(得分:1)

C99 rationale提供了有关如何以良好方式重新定义断言的示例(第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 

我在此代码之前包含assert.h以确保使用assert.h。 另请注意,它调用的函数会执行报告逻辑,因此您的代码会更小。

答案 3 :(得分:1)

在Cygwin / Windows和Linux上使用gcc会遇到同样的问题。

我的解决方案是覆盖实际断言失败处理函数的(弱)定义。这是代码:

/*!
 * 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();
}

答案 4 :(得分:-1)

您可以检查是否已定义NDEBUG,如果是,则打印您要打印的任何日志。