如何在运行时找到当前函数的名称?

45

多年来一直使用丑陋的MFC ASSERT宏,我终于决定放弃它并创建终极ASSERT宏。

获取文件和行号,甚至失败的表达式都没有问题。 我可以在其中显示一个带有Abort / Retry / Cancel按钮的消息框。

当我按下Retry按钮时,VS调试器跳转到包含ASSERT调用的行(而不是类似其他ASSERT函数的某个反汇编位置)。 因此,这基本上都在工作。

但是真正酷的是要显示失败的函数名

然后我就可以决定是否调试它,而不必从文件名猜测它在哪个函数中。

例如,如果我有以下函数:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   ASSERT(lpCreateStruct->cx > 0);
   ...
}

然后当ASSERT触发时,消息框会显示类似于:

Function = CMainFrame::OnCreate

那么,在运行时找到当前函数名最简单的方法是什么?

它不应该使用MFC或.NET框架,尽管我两者都在使用。
它应该尽可能地可移植。


如果您可以访问John Robbins的Debugging Applications for Microsoft® .NET and Microsoft Windows®,那么您一定要查看所提供CD上BugSlayerUtil库中的断言。它们是针对Windows特定的,但确实是终极的。 - Paul
我看了一下http://www.koders.com/cpp/fid3653A5E08C30DB8B7551729FBED0BC3D51B19AD8.aspx,但它似乎没有显示函数名。因此,我认为我的版本比他的更终极 :D - demoncodemonkey
它显示堆栈跟踪。堆栈跟踪包含函数名称,不是吗? - Paul
啊,我没有注意到那个。嗯,这可能很有用,但这不会让你可爱的消息框变得难以阅读吗?不过我理解你的意思了。也许在我的宏的第二版中会考虑这个功能 ;) - demoncodemonkey
由于某种原因,书籍的链接在答案中无法显示。这是链接:http://www.amazon.com/Debugging-Applications-Microsoft%C2%AE-Microsoft-Pro-Developer/dp/0735615365/。 - Paul
8个回答

60

您的宏可以包含__FUNCTION__宏。 毫无疑问,函数名将会编译时插入到扩展代码中,但它将是每次调用您的宏时的正确函数名。所以它“看起来像”发生在运行时;)

例如:

#define THROW_IF(val) if (val) throw "error in " __FUNCTION__

int foo()
{
    int a = 0;
    THROW_IF(a > 0); // will throw "error in foo()"
}

10
很遗憾,__FUNCTION__ 不是标准用法。__func__ 是标准用法,但它不是一个字符串字面量; 它的作用类似于一个变量,所以你不能轻易地将其连接起来。 - ephemient
3
我提到这个因为 OP 要求可移植性。 - ephemient
1
__FUNCTION__是胜利者,它运行得非常出色。它甚至告诉你命名空间。我不需要真正的跨平台性,但我的解决方案中只有一些项目使用MFC,因此该宏必须在没有MFC的情况下工作(这就是我所说的“尽可能便携”)。感谢所有做出贡献的人! - demoncodemonkey
还有一个__FUNCSIG__(同样是VC++特有的)-它包括函数的完整签名,而不仅仅是名称,并且很方便区分重载函数。 - Pavel Minaev
1
根据此链接此链接__FUNCTION____func__均不是宏。 - sancho.s ReinstateMonicaCellio
显示剩余3条评论

28

C++ 预处理器宏 __FUNCTION__ 给出函数的名字。

请注意,如果使用这个宏,它并不会在运行时真正获取文件名、行号或函数名。宏是由预处理器扩展并编译的。

示例程序:

#include <iostream>

void function1()
{
        std::cout << "my function name is: " << __FUNCTION__ << "\n";
}

int main()
{
        std::cout << "my function name is: " << __FUNCTION__ << "\n";
        function1();
        return 0;
}

输出:

我的函数名称是:main
我的函数名称是:function1

1
__FUNCTION__不是C++语言标准的一部分(我没有因为这个给你点踩,但我猜可能是其他人这么做了)。 - anon
好的观点,我认为我误读了某些内容。 根据http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html, __func__是C99标准的一部分(虽然不是所有东西都支持)。 __FUNCTION__不是,但如果大型编译器(MSDN / GCC)支持它,则它实际上是可移植的。 - YenTheFirst
1
请注意,func 也不是 C++ 的一部分。 - anon
谢谢,好答案。你说__FUNCTION__是可移植的,我就放过你了。对我来说,它足够可移植了 :) - demoncodemonkey
谢谢。 :) 我想我也意识到了我的错误。C99是C标准,而不是C ++。 - YenTheFirst
@YenTheFirst - 如果您意识到自己发布了错误的信息,您是否考虑修正答案而不仅仅是留下评论? - sancho.s ReinstateMonicaCellio

19

并没有标准的解决方案。不过,BOOST_CURRENT_FUNCTION是在所有实际情况下都可以使用的。该头文件不依赖于其他任何Boost头文件,因此如果整个库的开销无法接受,它可以独立使用。


1
谢谢,我目前还没有使用Boost,但至少我知道如果我需要移植它,有一个可以替代__FUNCTION__的东西。 - demoncodemonkey
{{BOOST_CURRENT_FUNCTION}}的头文件是独立的。你可以只下载它并将其放入你的代码库中,而不需要从Boost中获取其他任何内容。 - Andy Thomas


10

在GCC中,您可以使用__PRETTY_FUNCTION__宏。
微软也有一个等效的__func__宏,尽管我没有可用的版本进行尝试。

例如,在您的函数开头放置类似于此的内容来使用__PRETTY_FUNCTION__,您将获得完整的跟踪。

void foo(char* bar){
  cout << __PRETTY_FUNCTION__ << std::endl
}

将会输出

void foo(char* bar)

如果你想要输出更多的信息,所有标准的C/C++编译器下都可以使用__FILE____LINE__宏。

实际上,我有一个特殊的调试类,在其中使用适当的环境变量可以获得完整的程序跟踪。你也可以做类似的事情。这些宏非常方便,能够在现场打开选择性调试功能真的很棒。

编辑:显然__func__是标准的一部分?我不知道。不幸的是,它只给出函数名,而不是参数。我喜欢GCC的__PRETTY_FUNC__,但它不能在其他编译器上使用。

GCC还支持__FUNCTION__


1
对于微软而言,__FUNCSIG__ 的作用类似于 __PRETTY_FUNCTION__。(值得一提的是,它们和 __func__ 都不是宏。) - celticminstrel

5

您可以使用__FUNCTION__,该宏在编译时将被展开为函数名称。

以下是在assert宏中使用它的示例。

#define ASSERT(cond) \
    do { if (!(cond)) \
    MessageBoxFunction("Failed: %s in Function %s", #cond, __FUNCTION__);\
    } while(0)

void MessageBoxFunction(const char* const msg,  ...)
{
    char szAssertMsg[2048];

    // format args
    va_list vargs;
    va_start(vargs, msg);
    vsprintf(szAssertMsg, msg, vargs);
    va_end(vargs);

    ::MessageBoxA(NULL, szAssertMsg, "Failed Assertion", MB_ICONERROR | MB_OK);
}

@demoncodemonkey:Trevor提出了一个非常好的问题。为什么Andrew不应该在这里使用可变参数函数? - unforgettableidSupportsMonica

4

C++20 std::source_location::function_name

现在我们有了适当的标准化,不再需要宏:

main.cpp

#include <iostream>
#include <string_view>
#include <source_location>

void log(std::string_view message,
         const std::source_location& location = std::source_location::current()
) {
    std::cout << "info:"
              << location.file_name() << ":"
              << location.line() << ":"
              << location.function_name() << " "
              << message << '\n';
}

int f(int i) {
    log("Hello world!"); // Line 16
    return i + 1;
}

int f(double i) {
    log("Hello world!"); // Line 21
    return i + 1.0;
}

int main() {
    f(1);
    f(1.0);
}

编译和运行:

g++ -ggdb3 -O0 -std=c++20 -Wall -Wextra -pedantic -o source_location.out source_location.cpp
./source_location.out

输出:

info:source_location.cpp:16:int f(int) Hello world!
info:source_location.cpp:21:int f(double) Hello world!

请注意这个调用保留了调用者的信息,因此我们可以看到所需的 main 调用位置而不是 log

我在这里更详细地介绍了相关的标准。

在 Ubuntu 22.04,GCC 11.3 上进行了测试。


1
你可以轻松地使用 func。它将在运行时返回引发异常的当前函数名称。

用法:

cout << __func__ << ": " << e.what();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接