提问人:陈浩南 提问时间:5/20/2022 更新时间:9/18/2022 访问量:608
在仅标头库中使用 '[[GNU::Noinline]]'
using `[[gnu::noinline]]` in header-only library
问:
应声明仅标头库中的函数,以防止在不同的翻译单元中使用多个定义。也就是说,例如,我写了一个仅标头的库:inline
mylib.hpp
void do_something(int) {}
我在两个不同的 cpp 文件中使用了这个标头:
// a.cpp
# include "mylib.hpp"
void func1() {do_something(1);}
// b.cpp
# include "mylib.hpp"
void func2() {do_something(2);}
构建它们时,GCC 会抱怨“多重定义”。为了防止这种情况,定义函数,使其在每个翻译单元中都有一个副本(即,在最后一个输出中有两个副本),或者将函数定义为在最后一个输出中只有一个副本,这就是我们想要的。g++ -o main a.cpp b.cpp
do_something(int)
static void do_something(int) {}
inline void do_something(int) {}
但是,如果我想强制不被编译器内联(例如,我想使用回溯库来动态找出哪个函数调用了),我应该这样写:do_something
do_something
[[gnu::noinline]] inline void do_something(int) {}
然而,海湾合作委员会抱怨说:
Warning: inline declaration of ‘do_something(int)’ follows declaration with attribute ‘noinline’ [-Wattributes]
那么,做这些事情的正确方法是什么呢?
答:
关键字并不意味着代码必须内联,或者甚至会内联。 表示此函数可能有多个(相同)版本,链接器最多会选择一个代表。inline
inline
如果编译器认为函数值得,则可以内联函数,并且可以使其更有可能内联。但为什么不让编译器来决定呢?它不会无缘无故地内联函数。inline
但是,如果你一定要试过吗?函数不会导出到翻译单元的外部。每个翻译单元都可以具有相同函数的静态版本,而不会发生冲突。但请注意,这将是同一函数的多个副本,每个副本都有一个唯一的地址。static
static
如果我想强制编译器不内联do_something
没有便携式方法可以做到这一点。
inline
有一个令人困惑的名字,这与内联无关。你只需要:
[[gnu::noinline]] static void do_something(int) {}
我想使用回溯库来动态找出哪个函数调用了do_something
有功能属性 https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes。artificial
做这些事情的正确方法是什么?
在标头中定义函数的正确方法是使用关键字。static
如果你的意思是不内联一个函数,你就不会 - 编译器优化代码,函数被内联。如果希望功能可用于调试,请将编译器配置为不内联代码(如果要强制执行)。-fno-inline
评论
static
this doesn't actually solve the problem
消除警告的“正确”方法是将代码拆分为头 () 和源 () 文件。.h
.cpp
// mylib.h
[[gnu::noinline]]
void do_something(int);
// mylib.cpp
void do_something(int)
{
// Implementation
}
// a.cpp
#include "mylib.h"
void func1()
{
do_something(1);
}
// b.cpp
#include "mylib.h"
void func2()
{
do_something(2);
}
坦率地说,“仅标头”库是一个被高估的属性。 将代码拆分为头文件和源文件可以缩短编译时间,因为函数体只被分析和编译一次,而不是被多次分析和检查。(如果在标头中定义函数,编译器可能需要多次检查该函数,以检查该函数在转换单元之间是否未更改。
如果“仅标头”对您来说真的那么重要,那么其他选项是:
- 用。这将使警告静音,但存在生成同一函数的多个副本的风险 - 每个翻译单元一个副本。链接时间优化器可能能够消除重复项,但假设情况确实如此可能不是一个好主意。
static
- 将函数设为模板会使函数隐式化,同时仍遵循 ,但最终会得到一个虚拟模板参数,并强制人们在调用该函数时指定该参数。
inline
[[gnu::noinline]]
- 您可以将该函数设为类成员,这样就可以隐式地使用它,而不会破坏属性,并且只需要用户 () 进行一些额外的键入。
static
inline
Class::do_something(1)
另一种可行的方法,但确实很糟糕,应该避免:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
[[gnu::noinline]]
inline void do_something(int)
{
// Implementation
}
#pragma GCC diagnostic pop
但这是一个非常棘手的解决方案。当然,只有当 GCC 是编译器时,它才会起作用,并且会破坏许多其他编译器。(Clang 也许可以处理它,但 VC++ 会窒息它。
当然,您实际上可能不需要强制编译器来避免内联函数。通常,编译器会自行做出正确的决定。
就我个人而言,我唯一需要使用的时间是当函数使用内联程序集时,当编译器内联它时中断时,以及在测量函数调用的开销时。(后者可能是该属性的预期用途。[[gnu::noinline]]
评论
do_something
inline
Warning: inline declaration of
file:line: first declared here ---->
file:line: second time declared here --->
do_something()