C 中受保护的函数调用

Guarded function calls in C

我想实现以下目标:

假设我们有一些函数foo。我们还有两个函数 prepost 分别检查它的前置条件和后置条件。 我们有一些 foo 函数调用。现在我想用检查条件是否满足的 "guarded" 调用替换每个 foo 调用。这必须使用编译器标志进行切换。递归 foo 调用也必须替换。

我能想到的最好的事情如下:

#ifdef NDEBUG
int foo_orig(int n);

int foo(int n)
{
    int r;
    assert(pre(n));
    r = foo_orig(n);
    assert(post(n, r));
    return r;
}

int foo_orig(int n)
#else
int foo(int n)
#endif
{
    /* The body of the function foo... */
    foo(n); /* recursive call */
}

我知道这很笨拙和丑陋。有没有更好的方法呢?最好我只想要一个自动执行所有操作的宏,即

GUARD(foo, pre, post, arguments, return_type)

这可能吗?

编辑

建议将 prepost 调用嵌入到 foo 正文中。这可能适用于 pre,但对于 post,我们需要用相当复杂的代码替换每个 return 语句。如果在 foo body.

中更改了参数值,这也将不起作用

所以这更像是打字。我想要简单易用的东西。

你的意思是这样的?

#include <stdio.h>

#if 1
#define foo(...) GUARD(foo, pre, post, (__VA_ARGS__))
#define GUARD(F, PRE, POST, ARGS) (POST(F(PRE ARGS)))

//exactly equivalent to #define foo(...) post(foo(pre(__VA_ARGS__))) 
#endif

int pre(int n) { printf("pre %d\n", n); return n; }
int post(int n) { printf("post %d\n", n); return n; }

int (foo)(int n) {
    printf("%d\n", n);
    return n == 0 ? 0 : foo(n - 1);
}

int main(void) {
    foo(6);
}

#defined 宏的一个特点是它们不允许递归扩展,所以你总是可以定义一个像 #define foo(X) foo(Y(X)) 这样的宏——扩展中的 foo 是单独留下,并标记为不再更换。它专门用于此类用途,以便可以将函数调用无缝替换为同一调用的包装版本,而无需重命名,用于调试或其他此类目的。

(请注意,宏,不知道 C 的语法,如果有机会,也会替换 foo 声明 ,这是不好的;通常你' d 将宏定义放在主体之后或不同的文件中,但如果您需要递归包装器,您也可以使用鲜为人知的带括号的函数名语法来阻止它)

嗯。 NDEBUG 宏通常用于 抑制 调试代码(特别是断言),而不是导致包含调试代码。无论如何,是的,您可以创建一个宏,以可重用的方式为您执行此操作。但是,需要将 return 值插入到后置条件函数的参数中确实意味着您需要一个可变参数宏。

例如:

#ifdef NDEBUG
#define GUARD(ret, f, pre, post, ...) \
ret f ( __VA_ARGS__ )
#else
#define GUARD(ret, f, pre, post, ...) \
ret f##_orig ( __VA_ARGS__ );         \
ret f ( __VA_ARGS__ ) {               \
    ret r;                            \
    assert(pre ( __VA_ARGS__ ));      \
    r = f##_orig ( __VA_ARGS__ );     \
    assert(post(r, __VA_ARGS__ ));    \
    return r;                         \
}                                     \
ret f##_orig ( __VA_ARGS__ )
#endif

您可以这样使用它:

GUARD(int, foo, pre, post, int n)
{
    /* body of the real foo() */
}

如果您愿意为 prepost 函数使用标准模式(例如 foo_pre()foo_post()),那么您可以从宏中省略那些参数并通过标记粘贴构造它们,因为 foo_orig 名称也被构造。然后使用看起来像这样:

GUARD(int, foo, int n)
{
    /* body of the real foo() */
}