std::source_location::current() 返回行号1是因为它在宏展开位置由编译器内联注入位置信息;若封装成普通函数则记录函数定义处而非调用处的行号,必须在调用现场直接使用或通过默认参数/宏传递。

std::source_location::current() 在 C++20 中为什么返回的行号总是 1?
因为 std::source_location::current() 是一个 编译期内联函数,它不“运行”在调用点,而是由编译器在**宏展开位置**直接注入当前源码位置信息。如果你把它封装成普通函数再调用,比如:
std::source_location get_loc() {
return std::source_location::current(); // ❌ 这里记录的是 get_loc 函数体内的行号(通常是定义该函数的第一行)
}
那返回的 line() 就是 get_loc 函数体所在的行,而非调用 get_loc() 的那一行。这是最常踩的坑。
- 必须在需要记录位置的**调用现场直接使用**
std::source_location::current() - 不能跨函数传递——除非你把
std::source_location当作参数显式传入,并在调用方构造 - 所有主流编译器(GCC 11+、Clang 12+、MSVC 19.30+)都遵循这一行为,不是 bug,是设计使然
如何安全地把 source_location 传进日志函数?
典型做法是让日志函数接收一个默认参数,由编译器在调用点自动填充:
void log(const char* msg, const std::source_location loc = std::source_location::current()) {
printf("[%s:%d] %s\n", loc.file_name(), loc.line(), msg);
}
这样每次调用 log("error"),loc 就精准对应到 log("error") 所在的文件和行号。
- 默认参数必须是字面量表达式,
std::source_location::current()是唯一被允许的非常量默认值 - 不能写成
auto loc = std::source_location::current()再传参——这又掉回上一节的坑里 - 如果函数是模板或
inline,依然有效;但若函数声明在头文件外且未 inline,可能因 ODR 导致多个 TU 中生成不同位置信息(极少见,但需留意)
file_name() 返回的路径为什么很长甚至含绝对路径?
std::source_location::file_name() 返回的是编译器内部记录的完整路径字符串,取决于你编译时的 -I、工作目录和源文件引用方式。GCC/Clang 默认输出绝对路径,MSVC 可能相对路径更常见。
- 不要依赖路径格式做字符串匹配(比如
strstr(loc.file_name(), "src/")),它不稳定 - 如需裁剪,建议在日志函数里用
std::filesystem::path(loc.file_name()).filename().string()(C++17+)提取文件名 - 某些构建系统(如 Bazel、CMake with
compile_options("$<join:>")</join:>)可配合编译器 flag 控制路径标准化,但非标准行为
在宏里封装 source_location 会不会破坏调试信息?
不会,而且这是推荐做法——宏能确保 current() 展开在用户代码行:
#define LOG(msg) do { \
::log(msg, std::source_location::current()); \
} while(0)
调用 LOG("timeout") 时,std::source_location::current() 确实捕获的是 LOG("timeout") 所在行。
- 宏比默认参数更灵活:可同时注入
__func__、自定义级别、时间戳等 - 注意避免宏中出现分号冲突,用
do { ... } while(0)包裹是稳妥选择 - 如果用了
constexpr函数封装,只要它不带函数调用(即纯内联展开),也可行;但一旦有函数调用层级,位置就偏移了
真正难处理的不是语法,而是当 source_location 被无意中“抬升”到封装层时,你看到的行号和预期差了七八行却查不出原因——这时候要立刻检查是否多套了一层函数调用。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










