【问题标题】:Is it possible to force a function not to be inlined?是否可以强制不内联函数?
【发布时间】:2011-03-20 17:49:59
【问题描述】:

我想强制一个小函数不被编译为内联函数,即使它很简单。我认为这对于调试目的很有用。有没有关键字可以做到这一点?

【问题讨论】:

  • 调试器可以很好地处理内联函数。因此无需阻止内联。
  • 并非如此。在设置数千个断点时将调试器挂起数分钟的好方法。
  • 在单步执行优化代码时(您基本上必须在汇编语言级别执行此操作),有时很高兴看到对函数的调用,这样您就可以跟随您所在的位置,并一次性跳过它们-- 因为编译器对“简单”的看法可能不是你的。 (调试器可以很好地处理所有这些问题,但必须由计算机操作员实际完成工作......)
  • 除了调试之外,我还想这样做以进行分析。一个函数正在被内联,而我没有表明它应该是内联的。然而,这是我想在配置文件中看到的特定功能。
  • @thecoshman 有时您必须调试发布代码,或者您有发布代码的转储

标签: c++ visual-c++


【解决方案1】:

在 Visual Studio 2010 中,__declspec(noinline) 告诉编译器永远不要内联特定的成员函数,例如:

class X {
     __declspec(noinline) int member_func() {
          return 0; 
     }
};

编辑:此外,当使用 /clr 编译时,具有安全属性的函数永远不会被内联(同样,这是 VS 2010 特有的)。

不过,我认为它对调试毫无用处。

【讨论】:

  • 文档说它仅适用于成员函数:msdn.microsoft.com/en-us/library/kxybs02x.aspx。有没有办法让一个独立的函数非内联?
  • 需要注意的是,内联函数存在于调用它们的同一个翻译单元中,这意味着,例如,包含在该翻译单元中的任何全局静态变量都用于代替同一个全局变量静态变量也包含在声明/定义内联函数的文件中(非内联函数使用包含在其定义文件中的全局静态变量的副本)。因此,无论函数是否内联,它都可能在编译之间产生差异。
【解决方案2】:

__declspec(noinline) 用于 VC++。与手册页相反,这似乎适用于独立函数,我认为我从未将它用于成员函数。您可能 - 尽管请注意我从未有过 - 也想考虑使用优化标志,以便仅考虑 inline 函数进行内联,尽管这当然具有全局影响并且可能不是您想要的.

__attribute__((noinline)) 用于 gcc(以及一些支持 gcc 属性语法的不太常见的编译器)。我必须承认,我认为我从未真正使用过它,但它似乎就在那里。

(当然,这两种风格的注解出现在不同的地方,所以编写对两者都适用的代码有点烦人。)

我不确定它们如何与inline C++ 关键字交互;我只在调试时使用它们(当我只想在优化后保留一个特定的非内联函数而不是内联时)或在检查生成的代码时(我很困惑,因为随机的东西被内联了)。

【讨论】:

  • 它也适用于成员函数。一个有趣的注意事项是,它似乎也对包含函数进行了非内联调用(即使它们会内联) - VS 2012
  • 如何通过 cmake 检查使用了什么编译器:vc++ 或 gcc 并 make 定义 noinline?
【解决方案3】:

请记住,内联在函数调用站点是相关的,相同的函数在某些情况下可以内联,而在其他情况下不能内联。

如果您的函数在编译单元之外可见,那么即使它被内联在 all 当前使用它的位置,该函数的主体仍然必须可供以后想要调用它的任何人使用(通过与目标文件链接)。

为了使调用站点不内联,您可以使用指向函数的指针。

void (*f_ptr)(int); // pointer to function
volatile bool useMe = true; // disallow optimizations 
if (useMe)
   f_ptr = myFunc;
else
   f_ptr = useOtherFunc;

f_ptr(42); // this will not be inlined

【讨论】:

    【解决方案4】:

    简单:不要让编译器看到函数的定义。那么它不可能被内联。当然,只有当它的你的代码才有效。

    在调试第 3 方代码时...是的,这将很有用,尤其是如果您可以从远处删除第 3 方代码。任何调试过包含大量 shared_ptr 取消引用的代码的人都知道我在说什么。

    【讨论】:

      【解决方案5】:

      [[gnu::noinline]]属性

      我们还可以将 C++11 属性说明符语法与非标准 gnu::noinline 属性一起使用:https://en.cppreference.com/w/cpp/language/attributes

      gnu:: 部分在未来的 C++ 标准中被删除以提供标准化的 [[noinline]] 只是时间问题 :-)

      main.cpp

      [[gnu::noinline]]
      int my_func() {
          return 1;
      }
      
      int main() {
          return my_func();
      }
      

      编译和反汇编:

      g++ -ggdb3 -O3 -o main.out -std=c++11 -Wall -Wextra -pedantic-errors main.cpp
      gdb -batch -ex 'disassemble/r main' main.out
      

      [[gnu::noinline]]:

         0x0000000000001040 <+0>:     f3 0f 1e fa     endbr64 
         0x0000000000001044 <+4>:     e9 f7 00 00 00  jmpq   0x1140 <my_func()>
      
      

      没有[[gnu::noinline]]

         0x0000000000001040 <+0>:     f3 0f 1e fa     endbr64 
         0x0000000000001044 <+4>:     b8 01 00 00 00  mov    $0x1,%eax
         0x0000000000001049 <+9>:     c3      retq
      

      在 Ubuntu 19.10 上测试。

      【讨论】:

        【解决方案6】:

        许多编译器都可以执行跨翻译单元内联。 Visual Studio 已经拥有它五年了,我相信 GCC 现在可以做到——尤其是自从 OP 被标记为 Visual C++ 以来,他的编译器可以应付是一个公平的赌注。

        最简单的方法是获取函数的地址,然后用它做一些无意义的事情,比如调用它或将它传递给操作系统/外部库函数。编译器无法内联这种函数。

        为什么你会想要,IDK。

        @cmets:

        如果 OP srsly, srsly 需要这个,那么他可以将它编译为 lib 并静态链接到它。

        【讨论】:

        • 虽然获取函数的地址需要编译器提供它的非内联版本,但我不认为它要求它不要在其他地方内联它。
        • 这并不能阻止它内联函数。正如 sbi 正确指出的那样,它只会产生一个非内联版本。
        • 我很确定 VC++ 文档说它不会内联任何获取地址的函数。但是,你当然不能在翻译单元之间移动它,如果你不能获取它的地址,那么唯一可以确定的方法就是在外部库中编译它。
        • 抱歉吹毛求疵,但“前两个答案”有点无益。很难说你指的是哪个答案。
        • jalf:你几乎肯定是对的。写答案时没有太多时间。
        【解决方案7】:

        如果它是类的成员函数,则将其设为虚拟。

        【讨论】:

        • 不一定有效;如果方法可以去虚拟化,它仍然可以内联。
        【解决方案8】:

        您可以在头文件和 cpp 文件之间划分类实现。如果你把函数放在类定义之外,你的小函数不会是内联的。

        【讨论】:

        • 在某些(但不是所有)编译器上,这将起作用。 OTOH,无论谁对此投了反对票,我都对此感到不满,甚至没有发表评论解释原因。
        【解决方案9】:

        是否可以强制函数不被内联?

        我什至不会尝试回答这个问题,因为除了下面列出的两个原因之外,与此无关。

        内联主要是

        1. 一种对您几乎透明的优化
        2. 一种允许在标头中定义函数而不会出现多堆定义错误的方法

        (有人会切换这两者的顺序,但我坚持传统的顺序。)

        除非 A) 您绝对需要 define 某个标头中的函数或 B) 您正在分析和优化一段代码并且比编译器应该内联什么和不应该内联,内联应该与您无关。
        由于调试,这当然不应该是一个问题。您的调试器应该(并且在 VC 的情况下也可以)为您处理这些问题。

        【讨论】:

        • @sbi 我是反对者之一。一个真正的函数在目标代码中有一个符号,它比内联代码提供了几个好处。您可以从调试器或动态代码分析器中跟踪它们。使用分析器,您可以知道在函数中花费的确切时间,当代码的和平被合并到另一个更大的代码和平中时,这很难。您可以动态重命名函数,甚至在运行时用另一个函数替换此函数。
        • @sbi :这并不荒谬,您无法评估内联函数的成本。我所说的动态代码分析器是指 valgrind purify 或 insure++ 之类的工具。是的,我正在考虑后期绑定或重新绑定,我有时会使用它来跟踪。无论如何,我不知道汤姆森想要什么,但这可能与他无关,这就是我投反对票的原因。
        • 我对此投了反对票,因为它否定了 OP 的问题而不是回答它。
        • 我是通过 Google 搜索“visual c++ prevent inlining”来到这里的,所以,是的,对这个问题有一个真正的答案很有用的。就我而言,我想以可预测的顺序和位置获取变量在堆栈上,除了将它们PUSHed 作为函数的参数之外,我想不出任何其他方式。这样我的第一个变量总是在 [EBP+8],我的第二个变量总是在 [EBP+C] 等等。
        • 我也对你投了反对票。回答所提出的问题并就为什么替代解决方案可能更可取提供建议是一回事,但说“我什至不会尝试回答这个问题”并根据可能不正确的假设继续提供建议完全是另一回事OP在做什么。无论如何,有正当理由阻止内联。除了已经说明的之外,如果您正在对依赖项进行运行时模拟以进行测试,则不能模拟内联函数,并且您必须确保模拟本身没有内联,否则它们将不起作用。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-20
        • 2013-06-04
        • 2012-01-12
        相关资源
        最近更新 更多