如何找到调用函数的名称?
Posted
技术标签:
【中文标题】如何找到调用函数的名称?【英文标题】:How do I find the name of the calling function? 【发布时间】:2010-09-26 01:47:06 【问题描述】:我一直在使用 PRETTY_FUNCTION 来输出当前的函数名,但是我重新实现了一些函数,并想知道哪些函数正在调用它们。
在 C++ 中如何获取调用例程的函数名?
【问题讨论】:
这不是 C++ 语言的一部分。你的编译器可能有一些实用程序允许这样的事情,所以请指定你的环境。 【参考方案1】:这是您可以经常使用的解决方案。它的优点是无需更改实际的函数代码(无需添加对 stackwalk 函数的调用、更改参数以传入函数名称或链接到额外的库。)。为了让它工作,你只需要使用一点预处理器魔法:
简单示例
// orignal function name was 'FunctionName'
void FunctionNameReal(...)
// Do Something
#undef FunctionName
#define FunctionName printf("Calling FunctionName from %s\n",__FUNCTION__);FunctionNameReal
您必须暂时重命名您的函数,但请参阅下面的注释以获取更多建议。这将导致在调用函数的每个点出现printf()
语句。显然,如果是调用成员函数,或者需要捕获返回值(像传递函数调用和__FUNCTION__
到自定义函数,返回值相同类型...),但基本技术是相同的。您可能想要使用 __LINE__
和 __FILE__
或其他一些预处理器宏,具体取决于您拥有的编译器。 (此示例专门针对 MS VC++,但可能适用于其他示例。)
另外,您可能希望在标题中放置类似这样的内容,并由#ifdef
保护以有条件地打开它,这也可以为您重命名实际功能。
更新 [2012-06-21]
我收到了扩展答案的请求。事实证明,我上面的例子有点简单。以下是一些使用 C++ 处理此问题的完整编译示例。
带有返回值的完整源代码示例
将class
与operator()
一起使用使这非常简单。第一种技术适用于有和没有返回值的独立函数。 operator()
只需要反映与相关函数相同的返回值,并具有匹配的参数。
您可以使用g++ -o test test.cpp
编译它以获取非报告版本,使用g++ -o test test.cpp -DREPORT
编译该版本以显示调用者信息。
#include <iostream>
int FunctionName(int one, int two)
static int calls=0;
return (++calls+one)*two;
#ifdef REPORT
// class to capture the caller and print it.
class Reporter
public:
Reporter(std::string Caller, std::string File, int Line)
: caller_(Caller)
, file_(File)
, line_(Line)
int operator()(int one, int two)
std::cout
<< "Reporter: FunctionName() is being called by "
<< caller_ << "() in " << file_ << ":" << line_ << std::endl;
// can use the original name here, as it is still defined
return FunctionName(one,two);
private:
std::string caller_;
std::string file_;
int line_;
;
// remove the symbol for the function, then define a new version that instead
// creates a stack temporary instance of Reporter initialized with the caller
# undef FunctionName
# define FunctionName Reporter(__FUNCTION__,__FILE__,__LINE__)
#endif
void Caller1()
int val = FunctionName(7,9); // <-- works for captured return value
std::cout << "Mystery Function got " << val << std::endl;
void Caller2()
// Works for inline as well.
std::cout << "Mystery Function got " << FunctionName(11,13) << std::endl;
int main(int argc, char** argv)
Caller1();
Caller2();
return 0;
样本输出(报告)
Reporter: FunctionName() is being called by Caller1() in test.cpp:44
Mystery Function got 72
Reporter: FunctionName() is being called by Caller2() in test.cpp:51
Mystery Function got 169
基本上,在出现FunctionName
的任何地方,它都会用Reporter(__FUNCTION__,__FILE__,__LINE__)
替换它,其最终效果是预处理器通过立即调用operator()
函数来编写一些对象实例化。您可以使用g++ -E -DREPORT test.cpp
查看预处理器替换的结果(在 gcc 中)。 Caller2() 变成这样:
void Caller2()
std::cout << "Mystery Function got " << Reporter(__FUNCTION__,"test.cpp",51)(11,13) << std::endl;
您可以看到 __LINE__
和 __FILE__
已被替换。 (老实说,我不确定为什么__FUNCTION__
仍然显示在输出中,但是编译后的版本报告了正确的功能,因此它可能与多通道预处理或 gcc 错误有关。)
具有类成员函数的完整源代码示例
这有点复杂,但与前面的示例非常相似。我们不仅替换了对函数的调用,还替换了类。
与上面的示例一样,您可以使用 g++ -o test test.cpp
编译此代码以获得非报告版本,使用 g++ -o test test.cpp -DREPORT
编译显示调用者信息的版本。
#include <iostream>
class ClassName
public:
explicit ClassName(int Member)
: member_(Member)
int FunctionName(int one, int two)
return (++member_+one)*two;
private:
int member_;
;
#ifdef REPORT
// class to capture the caller and print it.
class ClassNameDecorator
public:
ClassNameDecorator( int Member)
: className_(Member)
ClassNameDecorator& FunctionName(std::string Caller, std::string File, int Line)
std::cout
<< "Reporter: ClassName::FunctionName() is being called by "
<< Caller << "() in " << File << ":" << Line << std::endl;
return *this;
int operator()(int one, int two)
return className_.FunctionName(one,two);
private:
ClassName className_;
;
// remove the symbol for the function, then define a new version that instead
// creates a stack temporary instance of ClassNameDecorator.
// FunctionName is then replaced with a version that takes the caller information
// and uses Method Chaining to allow operator() to be invoked with the original
// parameters.
# undef ClassName
# define ClassName ClassNameDecorator
# undef FunctionName
# define FunctionName FunctionName(__FUNCTION__,__FILE__,__LINE__)
#endif
void Caller1()
ClassName foo(21);
int val = foo.FunctionName(7,9); // <-- works for captured return value
std::cout << "Mystery Function got " << val << std::endl;
void Caller2()
ClassName foo(42);
// Works for inline as well.
std::cout << "Mystery Function got " << foo.FunctionName(11,13) << std::endl;
int main(int argc, char** argv)
Caller1();
Caller2();
return 0;
这是示例输出:
Reporter: ClassName::FunctionName() is being called by Caller1() in test.cpp:56
Mystery Function got 261
Reporter: ClassName::FunctionName() is being called by Caller2() in test.cpp:64
Mystery Function got 702
这个版本的亮点是一个装饰原始类的类,以及一个返回对类实例的引用的替换函数,允许operator()
进行实际的函数调用。
【讨论】:
+1 用于提供适用于所有操作系统的东西,即使它有点 hacky。只有一个问题:如果另一个类有同名的函数会怎样? @TimMeyer 你说的是函数名的 undef 吗?大概是坏事。 =D 在这种情况下,你可能不得不做一些更具侵略性的事情。 这简直太棒了。宏规则! @AndrewDunai 很高兴你喜欢它。在某些情况下,它们可能非常有用。我想它们有点像 goto 语句:有充分的理由回避,但在正确的地方是不可替代的。 =D【参考方案2】:这里有两个选项:
您可以通过GNU backtrace functions 获取最新版本的 glibc 的完整堆栈跟踪(包括调用函数的名称、模块和偏移量)。有关详细信息,请参阅my answer here。这可能是最简单的事情了。
如果这不是您想要的,那么您可以尝试libunwind,但这需要更多的工作。
请记住,这不是您可以静态知道的(与 PRETTY_FUNCTION 一样);您实际上必须遍历堆栈才能找出调用您的函数。所以这不是在普通的调试 printfs 中真正值得做的事情。但是,如果您想进行更认真的调试或分析,那么这可能对您有用。
【讨论】:
【参考方案3】:在 GCC 版本 ≥ 4.8 的情况下,您可以使用 __builtin_FUNCTION
— 不要与 __FUNCTION__
和类似名称混淆 — 这似乎有点晦涩。
例子:
#include <cstdio>
void foobar(const char* str = __builtin_FUNCTION())
std::printf("called by %s\n", str);
int main()
foobar();
return 0;
输出:
called by main
example on WandBox
【讨论】:
是的,它是一个函数。始终使用()
:__builtin_FUNCTION()
太糟糕了,似乎没有像 __builtin_PRETTY_FUNCTION()
这样的东西。【参考方案4】:
除非问题比您明确提出的更多,否则只需重命名函数并让编译器/链接器告诉您调用它的位置。
【讨论】:
当我说我正在输出它时,我的意思是在运行时。不是在编译时。【参考方案5】:Aaron 答案的变体。我不确定这个答案是否有这个问题,但是当你做一个#define function
时,它变成了一个全局变量,那么,如果你的项目有几个具有相同成员类函数名的类,所有类都会重新定义它们的函数名到相同的功能。
#include <iostream>
struct ClassName
int member;
ClassName(int member) : member(member)
int secretFunctionName(
int one, int two, const char* caller, const char* file, int line)
std::cout << "Reporter: ClassName::function_name() is being called by "
<< caller << "() in " << file << ":" << line << std::endl;
return (++member+one)*two;
;
#define unique_global_function_name(first, second) \
secretFunctionName(first, second, __FUNCTION__,__FILE__,__LINE__)
void caller1()
ClassName foo(21);
int val = foo.unique_global_function_name(7, 9);
std::cout << "Mystery Function got " << val << std::endl;
void caller2()
ClassName foo(42);
int val = foo.unique_global_function_name(11, 13);
std::cout << "Mystery Function got " << val << std::endl;
int main(int argc, char** argv)
caller1();
caller2();
return 0;
结果:
Reporter: ClassName::function_name() is being called by caller1() in D:\test.cpp:26
Mystery Function got 261
Reporter: ClassName::function_name() is being called by caller2() in D:\test.cpp:33
Mystery Function got 702
【讨论】:
【参考方案6】:在第一个近似值中,只需 grep 函数名的代码库。然后是 Doxygen,然后是动态日志(其他人讨论过)。
【讨论】:
【参考方案7】:您可以使用此代码来跟踪程序中最后 n 个点的控制点。用法:见下面的主要功能。
// What: Track last few lines in loci of control, gpl/moshahmed_at_gmail
// Test: gcc -Wall -g -lm -std=c11 track.c
#include <stdio.h>
#include <string.h>
#define _DEBUG
#ifdef _DEBUG
#define lsize 255 /* const int lsize=255; -- C++ */
struct locs
int line[lsize];
char *file[lsize];
char *func[lsize];
int cur; /* cur=0; C++ */
locs;
#define track do \
locs.line[locs.cur]=__LINE__ ;\
locs.file[locs.cur]=(char*)__FILE__ ;\
locs.func[locs.cur]=(char*) __builtin_FUNCTION() /* __PRETTY_FUNCTION__ -- C++ */ ;\
locs.cur=(locs.cur+1) % lsize;\
while(0);
void track_start()
memset(&locs,0, sizeof locs);
void track_print()
int i, k;
for (i=0; i<lsize; i++)
k = (locs.cur+i) % lsize;
if (locs.file[k])
fprintf(stderr,"%d: %s:%d %s\n",
k, locs.file[k],
locs.line[k], locs.func[k]);
#else
#define track do while(0)
#define track_start() (void)0
#define track_print() (void)0
#endif
// Sample usage.
void bar() track ;
void foo() track ; bar();
int main()
int k;
track_start();
for (k=0;k<2;k++)
foo();
track;
track_print();
return 0;
【讨论】:
您可能应该在track_print()
函数中执行if (!locs.file[k]) break;
。此外,如果您不想显示所有 stack,您可能应该在 track
宏中执行 locs.file[locs.cur] = NULL;
。这样你就有了一个可以停下来的地方。看起来打印也没有正确使用当前位置...【参考方案8】:
您可能想要所有可能调用它们的函数的名称。这基本上是调用图中的一组边。 doxygen 可以生成调用图,然后只需查看函数节点的传入边即可。
【讨论】:
这并不能告诉你是谁给你打电话的。它只会告诉你谁可以给你打电话。 图书馆呢。它不会告诉您哪些应用程序功能可能会调用您。【参考方案9】:结合 __builtin_return_address 和 dladdr 在 C++、C、Objective-C 和 Objective-C++ 中工作:
#include <dlfcn.h>
Dl_info info;
if (dladdr(__builtin_return_address(0), &info))
printf("%s called by %s", __builtin_FUNCTION(), info.dli_sname);
注意dladdr
需要一个动态链接的程序:
要动态链接您的程序,您可能需要添加 -rdynamic
或 -Wl,--export-dynamic
作为选项 (source)。
【讨论】:
【参考方案10】:Cflow 可用于获取用 C/C++ 编写的源代码的调用图。你可以解析这个调用图来得到你想要的。
【讨论】:
以上是关于如何找到调用函数的名称?的主要内容,如果未能解决你的问题,请参考以下文章