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

How do I find the name of the calling function?

提问人:Phil Hannent 提问时间:12/9/2008 最后编辑:Phil Hannent 更新时间:11/17/2023 访问量:99520

问:

我一直在使用 PRETTY_FUNCTION 输出当前函数名称,但是我已经重新实现了一些函数,并想知道哪些函数正在调用它们。

在 C++ 中,如何获取调用例程的函数名称?

C++ 调试

评论

8赞 Marcin 12/9/2008
这不是 C++ 语言的一部分。您的编译器可能有一些实用程序允许这样的事情,因此请指定您的环境。

答:

30赞 Todd Gamblin 12/9/2008 #1

以下是两个选项:

  1. 您可以使用带有 GNU 回溯函数的最新版本的 glibc 获得完整的堆栈跟踪(包括调用函数的名称、模块和偏移量)。有关详细信息,请参阅此处的答案。这可能是最简单的事情。

  2. 如果这不是您想要的,那么您可以尝试 libunwind,但它将涉及更多的工作。

请记住,这不是您可以静态知道的东西(就像PRETTY_FUNCTION一样);你实际上必须遍历堆栈才能弄清楚是什么函数调用了你。因此,这在普通的调试printfs中并不值得做。但是,如果您想进行更严肃的调试或分析,那么这可能对您有用。

1赞 MSalters 12/9/2008 #2

您可能需要所有可能调用它们的函数的名称。这基本上是调用图中的一组边。Doxygen 可以生成调用图,然后只需查看函数节点的传入边缘即可。

评论

1赞 Todd Gamblin 12/9/2008
这并不能告诉你到底是谁给你打电话的。它只告诉你谁可以给你打电话。
1赞 Martin York 12/10/2008
那么图书馆呢?它不会告诉您哪些应用程序函数可以调用您。
1赞 user3458 12/10/2008 #3

在第一个近似值中,只需 grep 函数名称的代码库即可。然后是 Doxygen,然后是动态日志记录(两者都由其他人讨论过)。

60赞 Aaron 12/18/2008 #4

这是您经常使用的解决方案。它的优点是不需要更改实际函数代码(无需添加对 stackwalk 函数的调用、更改参数以传入函数名称或链接到额外的库)。要让它工作,你只需要使用一些预处理器的魔力:

简单示例

// orignal function name was 'FunctionName'
void FunctionNameReal(...)
{
  // Do Something
}

#undef FunctionName
#define FunctionName printf("Calling FunctionName from %s\n",__FUNCTION__);FunctionNameReal

您必须暂时重命名函数,但有关更多建议,请参阅下面的注释。这将在调用函数的每个点生成一个语句。显然,如果你正在调用一个成员函数,或者需要捕获返回值(比如将函数调用传递给返回相同类型的自定义函数...),你必须做一些安排,但基本技术是相同的。您可能需要使用 和/或一些其他预处理器宏,具体取决于您拥有的编译器。(此示例专门用于 MS VC++,但可能适用于其他示例。printf()__FUNCTION____LINE____FILE__

此外,您可能希望在被守卫包围的标头中放置这样的东西,以有条件地打开它,这也可以为您重命名实际函数。#ifdef

更新 [2012-06-21]

我收到了扩展我的答案的请求。事实证明,我上面的例子有点简单化。以下是一些使用 C++ 处理此问题的完整编译示例。

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

使用 with 使这变得非常简单。第一种技术适用于有和没有返回值的独立函数。 只需要反映与相关函数相同的返回值,并具有匹配的参数。classoperator()operator()

您可以为非报告版本和显示调用方信息的版本编译此版本。g++ -o test test.cppg++ -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

基本上,无论发生什么,它都会将其替换为 ,其最终效果是预处理器通过立即调用函数来编写一些对象实例化。您可以使用 查看预处理器替换的结果(以 gcc 为单位)。Caller2() 变为:FunctionNameReporter(__FUNCTION__,__FILE__,__LINE__)operator()g++ -E -DREPORT test.cpp

void Caller2()
{
  std::cout << "Mystery Function got " << Reporter(__FUNCTION__,"test.cpp",51)(11,13) << std::endl;
}

你可以看到这一点,并且已经被替换了。(老实说,我不确定为什么仍然显示在输出中,但编译版本报告了正确的函数,因此它可能与多通道预处理或 gcc 错误有关。__LINE____FILE____FUNCTION__

具有类成员函数的完整源代码示例

这有点复杂,但与前面的示例非常相似。我们不仅替换了对函数的调用,还替换了类。

与上面的示例一样,您可以为非报告版本和显示调用方信息的版本编译此版本。g++ -o test test.cppg++ -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赞 Tim Meyer 3/9/2015
+1 提供适用于所有操作系统的东西,尽管它有点笨拙。只有一个问题:如果另一个类具有同名的函数,会发生什么?
0赞 Aaron 3/11/2015
您@TimMeyer谈论函数名称的 undef 吗?可能是坏事。=D 在这种情况下,您可能需要做一些更具侵入性的事情。
1赞 Andrew Dunai 10/28/2015
这简直太神奇了。宏统治!
3赞 Aaron 10/29/2015
@AndrewDunai很高兴你喜欢它。在某些情况下,它们可能非常有用。我想它们有点像 goto 语句:有充分的理由回避,但在正确的地方是不可替代的。=D
4赞 bgbarcus 6/22/2012 #5

除非问题比你明确提出的要多,否则只需重命名该函数,并让编译器/链接器告诉你它的调用位置。

评论

1赞 Phil Hannent 6/25/2012
当我说我正在输出它时,我的意思是在运行时。不在编译时。
40赞 Rusty Shackleford 5/15/2017 #6

使用 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

WandBox 上的示例

评论

1赞 ribamar 1/2/2018
是的,它是一个函数。始终使用:()__builtin_FUNCTION()
0赞 Unda 3/4/2019
太糟糕了,似乎没有类似的东西.__builtin_PRETTY_FUNCTION()
0赞 Timmmm 8/8/2022
此外,遗憾的是,这不包括类方法的类名。:-/
2赞 Timmmm 8/8/2022
但是,您可以使用,这可以说是更有帮助的。__builtin_LINE()__builtin_FILE()
1赞 mosh 5/22/2018 #7

您可以使用此代码来跟踪程序中最后 n 个点的控制位点。用法:见下面的main功能。

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

评论

0赞 Alexis Wilke 12/16/2021
你可能应该在函数中做。此外,如果您不想显示所有堆栈,您可能应该在宏中执行操作。这样你就有一个可以停下来的点。看起来打印件没有正确使用当前位置......if (!locs.file[k]) break;track_print()locs.file[locs.cur] = NULL;track
3赞 Evandro Coan 11/18/2018 #8

亚伦答案的变体。我不确定这个答案是否有这个问题,但是当你做一个 时,它就变成了一个全局变量,那么,如果你的项目有几个具有相同成员类函数名称的类,那么所有类的函数名称都会被重新定义为同一个函数。#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
-1赞 Shivam Kumar 7/23/2019 #9

Cflow 可用于获取用 C/C++ 编写的源代码的调用图。您可以解析此调用图以获取所需的内容。

0赞 Cœur 4/19/2021 #10

__builtin_return_addressdladdr 结合在 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);
}

请注意,需要动态链接的程序: 若要动态链接程序,可能需要将 or 添加为选项()。dladdr-rdynamic-Wl,--export-dynamic

0赞 c z 11/17/2023 #11

标准::source_location

例:
#include <iostream>
#include <source_location>

void some_function(std::source_location fun = std::source_location::current())
{
    std::cout << fun.function_name() << "\n";
}

void some_other_function()
{
    some_function();
}

int main()
{
    some_other_function();
}

输出:

void __cdecl some_other_function(void)