C 语言的范围卫士

发布于 2024-08-18 19:06:36 字数 324 浏览 12 评论 0原文

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

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

int function() {

  tic();

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

  toc();
  return 1;
}

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

谢谢

I would like to use scope guard in C in order to do profiling.

I would like to know how much time I spend in a function. Here is what I do:

int function() {

  tic();

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

  toc();
  return 1;
}

I need to place a toc statement each time I exit the function. I would like to do that without having to copy paste toc everywhere. Is there a generic way to do that, using a macro or something ?
Also I don't want to change the way the function is called, as there are many functions I have to profile.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

丢了幸福的猪 2024-08-25 19:06:36

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

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();

并且您的配置文件数据将低估函数中花费的时间。拥有一个真正的分析器如此好的另一个原因是它可以与编译器合作。

This doesn't change the way the function is called. Probably not much use if you want to be able to profile every single function, though.

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

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

As everyone else says: use a profiler, it will save you a lot of effort in the long run. As they don't say: if your platform has one.

If it doesn't, then the easiest might be to say (as a coding rule) that functions must have only one exit point, and that exit point must be via your macro. Then you can manually instrument all your functions with code at entry and exit. Legacy functions with multiple returns can be wrapped up as above.

Also, bear in mind when you're doing anything like this that your compiler can mess you up. You might write this:

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

If the compiler determines that something_else has no side-effects, then even though something_else takes significant time, it might turn the code into this:

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

And your profile data will under-estimate the time spent in your function. Another reason it's so good to have a real profiler - it can co-operate with the compiler.

桃扇骨 2024-08-25 19:06:36

您可以定义一个宏,例如:

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

它应该在您放置的任何地方都可以工作。然后,您可以自动将 return *; 替换为 TOC_RETURN(*)

You could define a macro like:

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

which should work anywhere you put it. Then you can automate replacing return *; with TOC_RETURN(*).

记忆之渊 2024-08-25 19:06:36

为什么不使用实际的分析工具,例如 gprof

Why not use an actual profiling tool, like gprof?

最单纯的乌龟 2024-08-25 19:06:36

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

#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;
}

免责声明:这可以被认为是丑陋和黑客的,因为:

  • 它不适用于 void 函数,除非您使用return;-语句。
  • 尽管它可以在 MSVC8 上运行,但它可能不是便携式/标准的。
  • 人们不应该定义关键字。

You could just "redefine" return via a macro: (please see Disclaimer)

#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;
}

Disclaimer: This can be considered ugly and hacky because:

  • It won't work for void functions unless you use return;-statements.
  • It might not be portable/standard, even though it works on MSVC8.
  • One shouldn't define keywords.
请叫√我孤独 2024-08-25 19:06:36

我来晚了,但是还有另一种方法可以使用 GCC 扩展在 C 中进行范围保护 cleanup 属性。 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.
}

I am very late to the party, but there is another way to do scope guarding in C using the GCC extension cleanup attribute. The cleanup attribute attaches a function to a variable declaration that is run when the variable goes out of scope. Originally intended to perform memory deallocation for dynamically allocated types, it can also be abused as a scope guard.

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;
}

This solution does not use macros, but you can of course wrap this into a macro. For example:

#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.
}
初心未许 2024-08-25 19:06:36

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

这样做不是更好吗?

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

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

PS 为什么不使用分析器?

I wouldn't recommend a macro for this. You profile the code just once in a while, and replacing 'return' with some special macro just for that purpose makes code less readable.

Isn't it better to do as follows?

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

This automatically handles "all exit points" from the function.

P.S. Why don't you use a profiler?

眼睛会笑 2024-08-25 19:06:36

真正的分析器不需要您修改代码,只需在启用分析的情况下编译它即可。

A real profiler doesn't need you to modify the code, just to compile it with profiling enabled.

树深时见影 2024-08-25 19:06:36

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

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

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

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

不要害怕检测分析器,它本质上会为您完成此操作。

Hmm, maybe wrap the function call in a macro (family of macros, really)? Here is one which takes no arguments and returns Retval:

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

You'll need macros for every arity of function calls you make, with a Retval and void returning version.

Edit Maybe there isn't even a point in defining the wrapper function, and better to just have a family of macros (again, for each arity and return type/void versions) which wrap a function call in a tic/toc directly at the callsites

Don't be afraid of instrumenting profilers, which essentially do this for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文