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

时间:2008-12-09 15:43:06

标签: c++ debugging

我一直在使用PRETTY_FUNCTION来输出当前的函数名,但是我已经重新实现了一些函数,并希望找出哪些函数正在调用它们。

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

10 个答案:

答案 0 :(得分:44)

这是您经常使用的解决方案。它的优点是不需要更改实际的功能代码(不添加对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 :(得分:21)

以下是两个选项:

  1. 您可以使用GNU backtrace functions的最新版glibc获取完整的堆栈跟踪(包括调用函数的名称,模块和偏移量)。有关详细信息,请参阅my answer here。这可能是最简单的事情。

  2. 如果这不是您正在寻找的,那么您可以尝试libunwind,但这将涉及更多工作。

  3. 请记住,这不是您可以静态知道的事情(与PRETTY_FUNCTION一样);你实际上必须走堆栈来找出你的功能。所以这不是普通调试printfs真正值得做的事情。但是,如果您想进行更严肃的调试或分析,那么这可能对您有用。

答案 2 :(得分:18)

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

在第1次近似中,只需grep函数名的代码库。然后是Doxygen,然后是动态记录(两者都讨论过)。

答案 7 :(得分:1)

您可以使用此代码跟踪程序中最后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;
} 

答案 8 :(得分:0)

在 C++、C、Objective-C 和 Objective-C++ 中结合使用 __builtin_return_addressdladdr

#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)。

答案 9 :(得分:-2)

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