C++如何获取函数签名?func与__PRETTY_FUNCTION怎么用

来源:MongoDB教程作者:椎名光头衔:网络博主
导读:本期聚焦于椎名光创作的《C++如何获取函数签名?func与__PRETTY_FUNCTION怎么用》,敬请观看详情。调试C++程序时,经常需要确认当前执行到哪个函数以及它的完整签名信息。C++11引入的__func__宏能在函数体内展开为该函数名的静态字符串,而编译器扩展__PRETTY_FUNCTION则提供更详细的返回类型、参数列表和命名空间等签名内容。二者在编译期确定,不带来运行时开销,适合日志追踪与断言输出。不同编译器对__PRETTY_FUNCTION的支持格式略有差异,MSVC使用__FUNCTION__和__FUNCSIG__,GCC与Clang则提供__PRETTY_FUNCTION__。理解它们的取值时机、作用范围以及截断处理,能帮你在跨平台项目中稳定地记录调用栈信息,而不必依赖外部调试符号。

在C++开发中,想要在程序运行期间知道“我现在在哪个函数”或者“这个函数的完整签名长什么样”,标准语言和编译器都给出了轻量的内置方案。C++11把__func__正式纳入标准,它是一个隐式定义的局部静态字符数组,保存了当前函数的名字;而在GCC、Clang这类编译器上,还提供了__PRETTY_FUNCTION__这个扩展,能给出包含返回类型、参数类型、类作用域甚至模板参数的“漂亮”签名。理解它们的机制,对写日志、做断言和排查调用链非常实用。

C++如何获取函数签名?func与__PRETTY_FUNCTION怎么用

__func__的基本用法与底层原理

__func__并不是宏,而是C++标准规定在每个函数体内部隐式声明的静态常量字符数组。编译器在编译阶段就把当前函数的名字写死进这个函数里,因此它没有运行时查询开销,也不会因为内联或者优化而失效。它的内容仅仅是函数名本身,比如int add(int, int)里的__func__就是"add",不包含返回值和参数列表。

由于它是静态数组,你可以直接把它当字符串字面量使用,也可以取地址传给日志函数。在类成员函数里,__func__不会带上类名,这点和编译器扩展不同。下面的例子展示了标准用法:

#include <iostream>
#include <string>

void demo(int x) {
    std::cout << "当前函数: " << __func__ << std::endl;
}

class Worker {
public:
    void run() {
        std::cout << "成员函数中 __func__ = " << __func__ << std::endl;
    }
};

int main() {
    demo(10);
    Worker w;
    w.run();
    return 0;
}

从上面代码可以看到,demo输出的是demoWorker::run里的__func__输出的是run。如果你需要区分不同类里的同名函数,仅靠__func__是不够的,这时候就要借助编译器提供的更详细签名工具。

__PRETTY_FUNCTION__能拿到什么信息

在GCC和Clang环境中,__PRETTY_FUNCTION__是一个编译器内置变量,展开为包含完整签名信息的字符串。它通常包括返回类型、函数名、参数类型列表,如果是成员函数还会带上类限定符、const修饰,模板函数则会显示出实例化的模板参数。它和__func__一样是编译期常量,不会引入运行时代价。

比如同样是上面的Worker::run,在GCC下__PRETTY_FUNCTION__可能是void Worker::run(),而一个模板函数template<typename T> T max(T a, T b)实例化后则会显示T max(T, T) [with T = int]这类信息。下面是一段展示差异的代码:

#include <iostream>

template <typename T>
T my_max(T a, T b) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return a > b ? a : b;
}

class Calc {
public:
    double process(double v) const {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return v * 2.0;
    }
};

int main() {
    my_max<int>(3, 5);
    Calc c;
    c.process(1.5);
    return 0;
}

通过这段输出,你可以清晰看到模板实例化和const成员函数的签名细节。对于写通用库或者需要自动生成调用日志的框架来说,__PRETTY_FUNCTION____func__更有价值,因为它几乎等于编译期帮你生成了函数原型字符串。

不过要注意,MSVC并不支持__PRETTY_FUNCTION__,它提供的是__FUNCTION__(类似__func__)和__FUNCSIG__(类似漂亮签名)。因此在跨平台代码里,通常需要用宏来统一,例如:

#if defined(_MSC_VER)
    #define FUNC_SIG __FUNCSIG__
#elif defined(__GNUC__) || defined(__clang__)
    #define FUNC_SIG __PRETTY_FUNCTION__
#else
    #define FUNC_SIG __func__
#endif

实际工程中的日志与截断处理

把函数签名写进日志是最典型的应用场景。由于__PRETTY_FUNCTION__的字符串可能较长,尤其模板深度嵌套时,直接全量打印会刷屏。实际项目中常配合std::string_view做前缀截取,或者只保留函数名部分。因为这些都是编译期常量,用std::string_view不会拷贝内存,非常高效。

另一个坑是:这些内置变量在函数外(比如全局作用域)是不可用的,它们只在函数体内部有效。如果你在头文件里写了内联函数并使用了__PRETTY_FUNCTION__,每个包含该头文件的翻译单元都会拿到自己的一份常量,这不会造成链接冲突,但会增加一点点二进制体积。下面示例展示如何安全截取签名前缀:

#include <iostream>
#include <string_view>

void log_entry(std::string_view sig) {
    constexpr size_t limit = 40;
    if (sig.size() > limit) {
        std::cout << "[log] " << sig.substr(0, limit) << "...\n";
    } else {
        std::cout << "[log] " << sig << "\n";
    }
}

int compute(int a, int b) {
    log_entry(__PRETTY_FUNCTION__);
    return a + b;
}

int main() {
    compute(1, 2);
    return 0;
}

从工程角度看,利用__func____PRETTY_FUNCTION__做轻量追踪,比运行时调用std::stacktrace或者解析符号表要省资源得多,尤其在嵌入式或高频调用路径上。只要做好编译器差异的宏封装和长度控制,就能在不变更构建系统的前提下,给所有关键函数加上“自报家门”的能力。

C++函数签名__func____PRETTY_FUNCTION修改时间:2026-08-23 03:12:12

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。