4

我想在 C 中使用范围保护来进行分析。

我想知道我在一个函数上花了多少时间。这是我所做的:

int function() {

  tic();

  ... do stuff ...
  if (something)
  {
    toc();
    return 0;
   }

  toc();
  return 1;
}

每次退出函数时,我都需要放置一个 toc 语句。我想这样做,而不必到处复制粘贴目录。有没有一种通用的方法来做到这一点,使用宏或其他东西?此外,我不想更改调用函数的方式,因为我必须分析许多函数。

谢谢

4

8 回答 8

5

这不会改变调用函数的方式。但是,如果您希望能够分析每个功能,可能没有多大用处。

static inline int real_function() {
    // previous contents of function(), with no tic or toc
}

int function() {
    tic();
    int r = real_function();
    toc();
    return r;
}

正如其他人所说:使用分析器,从长远来看,它将为您节省大量精力。正如他们没有说的那样:如果您的平台有一个。

如果没有,那么最简单的方法可能是(作为编码规则)函数必须只有一个退出点,并且该退出点必须通过您的宏。然后,您可以在进入和退出时使用代码手动检测所有功能。具有多个返回的旧函数可以像上面那样包装。

另外,请记住,当您执行此类操作时,编译器可能会搞砸您。你可以这样写:

tic();
do_something();
int i = something_else();
toc();
return i;

如果编译器确定 something_else 没有副作用,那么即使 something_else 需要大量时间,它也可能会将代码变成这样:

tic();
do_something();
toc();
return something_else();

而且您的个人资料数据会低估您在函数中花费的时间。拥有一个真正的分析器非常好的另一个原因 - 它可以与编译器合作。

于 2010-01-19T19:25:08.933 回答
4

您可以定义一个宏,如:

#define TOC_RETURN(x) \
    do { \
    toc(); \
    return x; \
    } while(0)

这应该可以在您放置它的任何地方使用。然后您可以自动替换return *;TOC_RETURN(*).

于 2010-01-19T18:57:43.680 回答
4

为什么不使用真正的分析工具,比如gprof

于 2010-01-19T18:59:40.367 回答
2

您可以通过宏“重新定义”返回:(请参阅免责声明)

#include <stdio.h>

void tic() { printf("tic\n"); }
void toc() { printf("toc\n"; }

#define return toc(); return
int foo() {
    tic();

    return 0;
}
#undef return

int main() {
    foo();
    return 0;
}

免责声明:这可以被认为是丑陋和骇人听闻的,因为:

  • 除非您使用return,否则它不适用于 void 函数;- 声明。
  • 它可能不是可移植的/标准的,即使它适用于 MSVC8。
  • 不应该定义关键字。
于 2010-01-19T19:05:05.987 回答
1

我不会为此推荐宏。您只是偶尔对代码进行一次分析,并且仅为此目的用一些特殊的宏替换“return”会降低代码的可读性。

这样做不是更好吗?

tic();
call_function();
toc();

这会自动处理函数的“所有退出点”。

PS你为什么不使用分析器?

于 2010-01-19T18:58:38.807 回答
1

真正的分析器不需要您修改代码,只需在启用分析的情况下对其进行编译。

于 2010-01-19T18:59:31.490 回答
1

我参加聚会的时间很晚,但是还有另一种方法可以使用 GCC 扩展cleanup属性在 C 中进行范围保护。该cleanup属性将函数附加到变量声明,当变量超出范围时运行。最初旨在为动态分配的类型执行内存释放,它也可以被滥用为范围保护。

void cleanup_toc(int *ignored __attribute__((__unused__))) { toc(); }

int function(void) {
    tic();
    int atexit __attribute__((__cleanup__(cleanup_toc))) = 0;

    //... do stuff ...
    if (something) {
        return 0;
    }

    return 1;
}

此解决方案不使用宏,但您当然可以将其包装成宏。例如:

#define CONCATENATE_IMPL(x, y) x ## y
#define CONCATENATE(x, y) CONCATENATE_IMPL(x, y)
#define ATEXIT(f) int CONCATENATE(atexit, __LINE__) __attribute__((__cleanup__(f))) = 0

int function(void) {
    ATEXIT(cleanup1); // These are executed in reverse order, i.e.
    ATEXIT(cleanup2); // cleanup2 will run before cleanup1.
}
于 2019-07-22T21:43:53.643 回答
0

嗯,也许将函数调用包装在一个宏中(真的是宏系列)?这是一个不带参数并返回 Retval 的方法:

// define the wrapper for name
#define DEFTIMECALL0(Retval,name) \
    Retval timed##name() \
    { \
        Retval ret;
        tic(); \
        ret = name(); \
        toc(); \
        return ret; \
    }

您需要为您进行的每个函数调用提供宏,并带有 Retval 和 void 返回版本。

编辑也许定义包装函数甚至没有意义,最好只有一个宏系列(同样,对于每个 arity 和返回类型/void 版本)直接在 tic/toc 中包装函数调用调用点

不要害怕检测分析器,它本质上是为你做的。

于 2010-01-19T18:58:09.057 回答