如何找到调用函数的名称?

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++ 处理此问题的完整编译示例。

带有返回值的完整源代码示例

classoperator() 一起使用使这非常简单。第一种技术适用于有和没有返回值的独立函数。 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++ 编写的源代码的调用图。你可以解析这个调用图来得到你想要的。

【讨论】:

以上是关于如何找到调用函数的名称?的主要内容,如果未能解决你的问题,请参考以下文章

如何在调用函数时打印它们?

如何仅使用函数名从 DLL 调用函数? C++

PHP里如何获取函数的调用者名称

如何调用名称以数字分隔的函数?从 t1 到 t20 的示例调用函数 - Python?

如何通过名称动态调用对象上的函数? [复制]

如何模拟未按名称调用的函数?