假设您的 C++ 编译器支持它们,是否有任何特别的理由不使用__FILE__
,__LINE__
以及__FUNCTION__
用于日志记录和调试目的?
我主要关心的是向用户提供误导性数据——例如,报告错误的行号或函数作为优化的结果——或者因此导致性能下降。
__FILE__
基本上,我可以信任__LINE__
并始终__FUNCTION__
做正确的事吗?
假设您的 C++ 编译器支持它们,是否有任何特别的理由不使用__FILE__
,__LINE__
以及__FUNCTION__
用于日志记录和调试目的?
我主要关心的是向用户提供误导性数据——例如,报告错误的行号或函数作为优化的结果——或者因此导致性能下降。
__FILE__
基本上,我可以信任__LINE__
并始终__FUNCTION__
做正确的事吗?
__FUNCTION__
是非标准的,__func__
存在于 C99 / C++11 中。其他(__LINE__
和__FILE__
)都很好。
它总是会报告正确的文件和行(如果您选择使用__FUNCTION__
/ ,它会报告功能__func__
)。优化不是一个因素,因为它是编译时宏扩展;它永远不会以任何方式影响性能。
在极少数情况下,将给出的行更改为其他内容可能很有用__LINE__
。我已经看到 GNU configure 这样做是为了在一些测试中报告适当的行号,因为它在原始源文件中没有出现的行之间插入了一些巫术。例如:
#line 100
将使以下行以__LINE__
100 开头。您可以选择添加新文件名
#line 100 "file.c"
它很少有用。但如果需要,我知道没有其他选择。实际上,也可以使用宏来代替行,它必须导致上述两种形式中的任何一种。使用 boost 预处理器库,您可以将当前行增加 50:
#line BOOST_PP_ADD(__LINE__, 50)
我认为提及它很有用,因为您询问了__LINE__
and的用法__FILE__
。人们永远不会从 C++ 中得到足够的惊喜 :)
编辑: @Jonathan Leffler 在评论中提供了一些更好的用例:
对于希望在用户的 C 代码中报告的错误与用户的源文件保持一致的预处理器,使用 #line 非常有用。Yacc、Lex 和(对我来说更熟悉的)ESQL/C 预处理器就是这样做的。
仅供参考:g++ 提供了非标准的 __PRETTY_FUNCTION__ 宏。直到刚才我还不知道 C99 __func__ (感谢 Evan!)。我想我仍然更喜欢 __PRETTY_FUNCTION__ 当它可用于额外的类范围时。
PS:
static string getScopedClassMethod( string thePrettyFunction )
{
size_t index = thePrettyFunction . find( "(" );
if ( index == string::npos )
return thePrettyFunction; /* Degenerate case */
thePrettyFunction . erase( index );
index = thePrettyFunction . rfind( " " );
if ( index == string::npos )
return thePrettyFunction; /* Degenerate case */
thePrettyFunction . erase( 0, index + 1 );
return thePrettyFunction; /* The scoped class name. */
}
C++20std::source_location
C++ 终于添加了一个非宏选项,当 C++20 普及时,它可能会在未来某个时候占据主导地位:
文档说:
constexpr const char* function_name() const noexcept;
6 返回:如果此对象表示函数体中的一个位置,则返回一个实现定义的 NTBS,该 NTBS 应对应于函数名称。否则,返回一个空字符串。
其中 NTBS 表示“空终止字节字符串”。
该功能存在于 GCC 11.2 Ubuntu 21.10 和-std=c++20
. 它不在 GCC 9.1.0 上,带有g++-9 -std=c++2a
.
https://en.cppreference.com/w/cpp/utility/source_location显示用法是:
主文件
#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 main() {
log("Hello world!");
}
编译并运行:
g++ -std=c++20 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
输出:
info:main.cpp:17:int main() Hello world!
__PRETTY_FUNCTION__
对__FUNCTION__
对__func__
对std::source_location::function_name
就个人而言,除了调试消息之外,我不愿意将这些用于任何事情。我已经做到了,但我尽量不向客户或最终用户展示这类信息。我的客户不是工程师,有时也不精通计算机。我可能会将此信息记录到控制台,但正如我所说,除了调试版本或内部工具外,我很不情愿。不过,我想这确实取决于您拥有的客户群。
我一直在使用它们。我唯一担心的是在日志文件中泄露 IP。如果你的函数名真的很好,你可能会让商业秘密更容易被发现。这有点像附带调试符号,只是更难找到东西。在 99.999% 的情况下,不会有什么不好的事情发生。