在C++开发中,想要在程序运行期间知道“我现在在哪个函数”或者“这个函数的完整签名长什么样”,标准语言和编译器都给出了轻量的内置方案。C++11把__func__正式纳入标准,它是一个隐式定义的局部静态字符数组,保存了当前函数的名字;而在GCC、Clang这类编译器上,还提供了__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输出的是demo,Worker::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