为了调试递归程序,我发现可视化我的函数调用的嵌套程度是多么有用。我喜欢有像__func__
这样的东西,但是我的堆栈跟踪的深度而不是我的函数名称。我知道编译器不可能简单地知道这一点,因为你是如何嵌套的是一个动态生成的值。但是编译器添加实现它的功能并不困难,你可以在每个call
之前向全局计数器添加1并在吃ret
之前减去1。
我正在使用以下调试语句(找到here):
#define printdbg(Level, formatString, ...) \
do { \
fprintf(stderr, "%s:%d %s: " formatString "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__); \
if (Level == LEVEL_ERROR) { printf("quitting\n"); exit(1); }\
} while (0)
我想在开始时添加一个额外的预定义标识符,我可以利用printf("%*s", __NEST__+1, ":")
的形式,在每个调试语句的开头打印总共__NEST__
空间,让我可视化每个调试的堆栈深度声明是由。
我知道我可以简单地拥有一个全局计数器,我在每个函数的开头都是++
,最后是--
,但我刚刚学习了预定义的标识符,它们真是太酷了!此外,无需重新发明轮子。
我无法在网上找到支持的预定义标识符列表。我发现的只有this和this,两者都声称是全面的。如果相当于__NEST__
,那么这里的某个人可能知道我正在寻找的那个词。如果它不存在,那么我在哪里可以找到所有预定义标识符的详细列表?
C不提供预定义的标识符,它将为您提供函数调用嵌套级别。如果运行时系统支持这样的标识符,则会给所有函数调用增加一些开销。开销将是每个人都愿意支付的价格,只有使用标识符的少数人才会受益。这违背了C编程语言的精神,您希望只为您正在使用的功能付费。
在大多数当前的CPU体系结构和C编译器中,您可以通过查看局部变量的地址来获得随每个函数调用而增加的数字。这是一个例子。
#include <stdio.h>
// Base nesting level (must be initialized by main)
static char *main_nesting;
// Return an integer corresponding to the current function call nesting level
int
nesting_level(void)
{
int a;
return (main_nesting - (char *)&a);
}
void
nest3(void)
{
printf("%s=%d\n", __func__, nesting_level());
}
void
nest2(void)
{
printf("%s=%d\n", __func__, nesting_level());
nest3();
}
void
nest1(void)
{
printf("%s=%d\n", __func__, nesting_level());
nest2();
}
int
main(int argc, char *argv[])
{
main_nesting = (char *)&argc;
printf("%s=%d\n", __func__, nesting_level());
nest1();
}
运行此程序时,您将获得如下输出。
main=20
nest1=68
nest2=116
nest3=164
预定义的宏名称集在§6.10.8 of the ISO/IEC 9899:2018 C standard中指定。此外,对于类似GCC的编译器,您可以通过在Unix系统上运行以下命令来获取所有预定义宏的列表。
cpp -dM /dev/null