【问题标题】:assert macro implementation in C89, how to exit the program?C89中的assert宏实现,如何退出程序?
【发布时间】:2021-05-06 14:54:51
【问题描述】:

我正在尝试在 C89 标准中实现我自己的 assert 宏。

我希望它和原来的一模一样:

dir/file.c:20: MyFunction: Assertion `null != pointer` failed.

有2个问题:

  1. 没有打印函数名称的选项,因为预标识符 __FUNC__ 仅在 c99 标准后可用。
  2. 我不知道如何退出程序。我尝试了exit(1)__Exit(1),但它们都不起作用,我认为这是因为macros 在每个处理阶段被转换为代码,这意味着预处理器甚至不知道这些是什么@ 987654328@ 功能尚未。因为它们只在编译阶段相关,对吧?

这是我的代码:

/********************************* Inclusions *********************************/
#include <stdio.h>  /* printf, NULL */

/***************************** Macros Definitions *****************************/
#define ASSERT(expr) \
    if (!(expr)){ \
        fprintf(stderr, "%s:%d: Assertion `%s` failed.\n" \
                ,__FILE__, __LINE__, #expr); }

/******************************************************************************/
int main()
{
    void *null_ptr = NULL;
    
    ASSERT(NULL != null_ptr);
    
    printf("ALL WORKS");
    
    return (0);
}

/******************************************************************************/

我的输出是:

`file.c:25: Assertion `NULL != null_ptr` failed.`

有什么方法可以获取函数名或者用宏退出程序吗? 因为现在,我没有得到函数的名称,更重要的是,即使断言打印了错误消息,程序也没有停止。

这很奇怪,因为无法获取函数名或使用宏退出程序,但原始的assert 却可以做到这两点?

P.S __FILE__ 每个标识符只为我打印文件名,就像 file.c 而不是 dir/file.c 就像原来的 assert 那样。这是为什么呢?

我希望我能写出类似的东西:

#define ASSERT(expr) \
        if (!(expr)){ \
            fprintf(stderr, "%s:%d: %s: Assertion `%s` failed.\n" \
                    ,__FILE__, __LINE__, __FUNC__, #expr); exit(1) }

谢谢。

【问题讨论】:

  • 如果您在宏中添加分号和#include &lt;stdlib.h&gt;,则宏中的exit 应该可以工作
  • 预处理器不需要关心exit()是什么意思。它只需将该函数调用放入代码中。编译器和链接器会处理它。
  • "我希望它和原版一模一样:" 什么是“原版”?你从哪里得到那个输出?还有“但他们俩都不工作”“不工作”是什么意思?会发生什么?
  • assert 不需要打印函数名称。它需要以实现定义的方式打印文件名和行号以及参数的文本。然后需要调用abort(不是exit)。
  • 请注意,以这种方式在宏中使用if 是错误的;如果程序员写if (cond) ASSERT(expr); else other_stuff(); 它不会像他们期望的那样表现。这就是需要do while hack 的原因。但是,正如@mediocrevegetable1 在他们的回答中所说,您的宏无论如何都应该扩展为表达式,因此您需要使用? : 而不是if

标签: c macros c-preprocessor assert exit


【解决方案1】:
  1. 确实,C89 没有办法获取函数名。因此,如果您只能依赖 C89,那么您将不得不这样做。请注意,甚至在 C99 之前,许多实现可能已经为此提供了自己的扩展,并且可能在自己的 assert() 定义中使用了这些扩展;例如GCC 有__FUNCTION__

  2. 如果断言失败,标准的assert() macro 会调用abort()。所以如果你想复制它的行为,你也可以这样做。

【讨论】:

  • 感谢您的回答。对于我的P.S 部分,您知道为什么会这样吗?为什么标准assert 将文件名打印为dir/file.c 而当我使用__FILE__ 时,它只打印文件名而不打印目录。只有file.c ?
  • 在时间/内存/安全/其他方面有什么理由更喜欢中止而不是退出?
  • @NoobCoder:C 标准(如果您想如此密切地遵循它会很好阅读!)只说__FILE__ 具有实现定义的格式;它是否包含路径取决于实现。 assert() 完全有可能使用了一些特定于实现的魔法,这些魔法与该实现为 __FILE__ 所做的不同。如果您无法使用超出标准提供的任何内容,那么您将无能为力。
  • @NoobCoder:abort()exit() 之间的主要区别在于abort() 通常的行为方式对调试更有用。例如,在 Unix 上,如果用户或系统管理员启用了核心转储,它可以创建核心转储,而 exit() 从不这样做。链接中提到的其他一些差异;例如abort() 不调用 atexit() 函数(如果程序已知处于不可能的状态,这通常是一件好事),它可能会或可能不会刷新打开的文件。
  • @NoobCoder:作为一般原则,您不必期望能够使用其他部分完全复制 C 标准的一部分。事实上,标准提供了许多功能,正是因为它们无法按照其余部分来实现。
【解决方案2】:

Nate Eldredge 回答了您的大部分问题。作为对您的 P.S. 的回应,我怀疑这是编译器可以在内部做而我们做不到的事情。与函数名称相同,但没有 __func__(尽管 GCC 有一个可以使用的 __FUNCTION__ 宏)。

您仍然可以使您的assert 更接近编译器assertassert 试图尽可能地模拟一个函数。因此,首先,它必须作为一个表达式工作,而你的不是因为if。此外,它应该返回voidman page on assert 给出了这个“原型”:

void assert(scalar expression);

这两个都是可能的。这是我刚刚制作的assert,(我认为)设法满足这两个要求:

#define ASSERT(expr)                                                        \
    ((expr) ?                                                               \
        (void) 0 :                                                          \
        (void) (fprintf(stderr, "%s:%d: %s: Assertion `%s` failed\n",       \
                        __FILE__, __LINE__, __FUNCTION__, #expr), abort()))

这利用了__FUNCTION__ GCC 扩展,如果你愿意,你可以删除它。

【讨论】:

  • 非常感谢您详尽的回答。除了__func__,您编写的所有内容都有效。既不是__FUNCTION__。我正在使用 GCC 扩展进行编译,但它仍然无法正常工作。 ISO C does not support ‘__FUNCTION__’ predefined identifier [-Wpedantic]
  • 我是这样编译的:gcc -ansi -pedantic-errors -Wall -Wextra -g
  • @NoobCoder 就像我说的,__FUNCTION__ 是扩展,而不是标准 C。由于您的 -pedantic-errors,当您尝试执行任何违反标准的操作时,编译器会抛出错误。由于__FUNCTION__ 不是标准的一部分,因此会引发错误。而且由于__func__是在C99中引入的,它在C89中并不存在,所以又是一个错误。
猜你喜欢
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-16
相关资源
最近更新 更多