更好的 C 语言断言

Better Assert in C

有时我必须通过 canbus 发送断言的结果,有时是本地的。 我只使用 C,Embitz 编译器使用 GCC、STM32F407 或 STM32F103。 我现在的主张是: .h 文件:

extern char *astrbuf;
#define assert(left,operator,right)\
if(!((left) operator (right))) \
 {asprintf(&astrbuf,"\nAssert error %s %d %ld %ld\n",__FILE__, __LINE__,\
  (u32)(left),(u32)(right));\
  asserted();\
 }

.c 文件:

void asserted(void)
{ dprint("%s",astrbuf);

后跟显示代码或 canbus 代码。 例子: 断言(毫秒,<,最大时间); 这工作得很好,但如果可以指示操作员会更好。 我根本看不出如何显示或发送运算符,可以是 ==<>.

您可以使用字符串化运算符#将宏参数operator转换为字符串#operator:

extern char *astrbuf;
#define assert(left,operator,right)\
if(!((left) operator (right))) \
 {asprintf(&astrbuf,"\nAssert error %s %d %ld %s %ld\n",__FILE__, __LINE__,\
  (u32)(left), #operator, (u32)(right));\
  asserted();\
 }

为什么不使用标准 assert 接口并包含整个表达式?

#define assert(EXPR) \
if (!(EXPR)) \
 {asprintf(&astrbuf, "\nAssert error %s %d %s\n",__FILE__, __LINE__, #EXPR); \
  asserted(); \
 }

... 使用 # 宏字符串化运算符。

顺便问一下,为什么你的代码一半在宏中,另一半在 asserted 函数中?为什么不在一个地方全部完成?

#define assert(EXPR) \
if (!(EXPR)) \
 { \
  asserted(__FILE__, __LINE__, #EXPR); \
 }

void asserted(const char *file, int line, const char *expr) {
    char *astrbuf;
    asprintf(&astrbuf, "%s: %d: assertion failed: %s\n", file, line, expr);
    dprint("%s", astrbuf);
    ...
}

现在您不再需要全局变量了。

还有一个潜在的问题。如果您这样使用宏:

if (foo())
    assert(x > 42);
else
    bar();

... else bar(); 部分将附加到隐藏在 assert 中的 if 语句,而不是外部 if。要解决此问题,您可以将整个内容包装在 do while 循环中:

#define assert(EXPR) \
    do { \
        if (!(EXPR)) { \
            asserted(__FILE__, __LINE__, #EXPR); \
        } \
    } while (0)

或者确保整个宏扩展为单个表达式:

#define assert(EXPR) \
    ((void)((EXPR) || (asserted(__FILE__, __LINE__, #EXPR), 0)))

当然你也可以把条件逻辑放在函数中:

#define assert(EXPR) asserted(!!(EXPR), __FILE__, __LINE__, #expr)
void asserted(int cond, const char *file, int line, const char *expr) {
    if (cond) {
        return;
    }
    ...
}

也许您可以尝试稍微不同的方法来实现相同的目标。

不要将 (left, operator, right) 传递到宏中,而是尝试传递单个布尔条件。您可以在实际断言函数中使用条件,也可以使用宏将其字符串化。这样您仍然可以将整个情况报告给您的调试模块 (canbus)。

这也适用于更复杂的表达式,例如 ((a-b)< 0)

#define assert( condition ) custom_assert( condition , STRINGIFY_CONSTANT( condition ), __FILE__, __LINE__)

stringify 宏在其自己的头文件中,并基于此 link 派生。 https://gcc.gnu.org/onlinedocs/gcc-3.4.3/cpp/Stringification.html

#define STRINGIFY_CONSTANT(a) STRINGIFY_CONSTANT_DO_NOT_USE(a)
#define STRINGIFY_CONSTANT_DO_NOT_USE(a) #a

显然,不要使用 STRINGIFY_CONSTANT_DO_NOT_USE

void custom_assert( int condition , const char * condition_string, const char * file_name, int line_number)
{
    if (!condition)
    {
        dprint("Assert Failed:'%s' File:'%s' Line:'%d'",condition_string, file_name, line_number);
    }
}

我会避免在您的断言#define 中放置超过单个函数调用的任何内容,因为这可能难以调试,并且还会增加代码的大小。我建议将任何逻辑放入函数中。

我称我的断言为 custom_assert。我还有许多#defines 将调试输出放到不同的通道,如 usb、rs232、屏幕显示等。在发布模式下,断言只是重新启动嵌入式设备,这在我的应用程序中是可以接受的。

Assert是一个宏,按照我的理解,它总是内联的。 机器的代码有很多断言来防止损坏,因此需要尽可能快地保持代码,即。正常情况下内联。 当出现任何问题时,使用断言的功能,速度不再是问题,安全才是。断言的功能会关闭电机等,并通过 canbus 或本地显示进行报告。我不想在宏中完成所有这些。 这就是为什么部分代码在宏中,部分在函数中的原因。 如果我的理解有误,请纠正我。 没有全局变量是一个明确的优势,我添加了 while (0),就像在其他宏中一样。