为什么这个__METHOD__NAME__需要内存副本?



创意来自https://stackoverflow.com/a/15775519/1277762

我在string_view的末尾添加了一个'',这样我们就可以使用data()来打印tf, spdlog等。

我用这个宏打印函数名和类名。

然而,我发现编译器没有足够的智能来内联字符串,而是需要一个内存副本来堆栈:

https://godbolt.org/z/bqob37G3z

查看main函数中CALL和CALLFUNC的区别

是否有可能告诉编译器只是把字符串放在一些RO部分,如const char *?

template<std::size_t N>
consteval const std::array<char, N> __get_function_name(const char * arr)
{
std::array<char, N>         data {};
std::string_view            prettyFunction(arr);
size_t                      bracket = prettyFunction.rfind("(");
size_t                      space = prettyFunction.rfind(" ", bracket) + 1;
size_t                      i;
for (i = 0; i < bracket - space; i += 1) {
data[i] = arr[space + i];
}
data[i] = '';
return data;
}
#define __METHOD_NAME__ __get_function_name<strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__).data()

谢谢@n.m。和user17732522。我终于得到了一个可行的版本:https://godbolt.org/z/sof1j3Md4

仍然不完美,因为这个解决方案需要搜索'('和' '两次,这可能会增加编译时间。


更新:只调用rfind(") once: https://godbolt.org/z/zYcajqaje

#include <array>
#include <string_view>
constexpr size_t my_func_end(const char * arr)
{
std::string_view prettyFunction(arr);
return prettyFunction.rfind("(");
}
constexpr size_t my_func_start(const char * arr, const size_t end)
{
std::string_view prettyFunction(arr);
return prettyFunction.rfind(" ", end) + 1;
}

template<std::size_t S, std::size_t E>
constexpr const std::array<char, E - S + 1> my_get_function_name(const char * arr)
{
std::array<char, E - S + 1> data {};
size_t i;
for ( i = 0; i < E - S; i += 1) {
data[i] = arr[S + i];
}
data[i] = '';
return data;
}
template<auto tofix>
struct fixme
{
static constexpr decltype(tofix) fixed = tofix;
};
#define __METHOD_NAME_ARRAY__(x)    my_get_function_name<my_func_start(x, my_func_end(x)), my_func_end(x)>(x)
#define __METHOD_NAME__             (fixme<__METHOD_NAME_ARRAY__(__PRETTY_FUNCTION__)>::fixed.data())

不确定编译器是否可以缓存计算。如果是,那就足够好。

编译器没有意识到或考虑到puts的具体行为,即它不存储传递给它的指针,也不再次调用它的调用者。

问题是,如果没有这些知识,编译器必须考虑到puts将存储指针传递给全局变量的可能性,然后再次调用其调用者,然后将新的指针参数与存储在全局变量中的旧指针参数进行比较。它们必须比较不相等,因为它们是指向不同临时对象的指针,都在它们的生命周期内。因此,编译器不能重用相同的只读静态内存位置作为puts调用的参数。

所以你需要显式地告诉编译器使用静态内存位置:
#define METHOD_NAME get_function_name<my_strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__)
#define CALL() { static constexpr auto v = METHOD_NAME; puts(v.data()); }

技术上v上的constexpr与功能上的consteval是冗余的。只有constexpr在两个也足够了。

如果你没有在v上添加constexpr,你可能想要添加const,虽然确保编译器不需要考虑puts将修改字符串内容的可能性,尽管在你的例子中GCC似乎意识到这一点。(putsconst char*为参数并不足以证明这一点)


你不能在那里使用strlen顺便说一下(假设你想把它移植到GCC以外的其他编译器上,这些编译器以你使用它的方式支持__PRETTY_FUNCTION__,例如Clang与libc++)。GCC在没有诊断的情况下允许它是不符合标准的,并且不能保证它可以在其他编译器上工作。std::strlen在标准中没有标记constexpr。你可以用std::char_traits<char>::length代替,从c++ 17开始就是constexpr

最新更新