如何告诉编译器不优化某些代码?

时间:2009-03-20 21:36:27

标签: c++ optimization templates compiler-construction function

有没有办法告诉编译器(在我的情况下为g ++)优化某些代码,即使该代码无法访问?我只想在目标文件中使用这些符号。

示例:这是一个简单的函数,我希望编译这个函数,即使它从未被调用过。

void foo(){
  Foo<int> v;
}

如果没有正式的编译器指令,是否有一个技巧可以让编译器认为它是一个重要的函数?或者至少让它认为它不能被安全地忽略?我试过这样的事情:

extern bool bar;
void foo(){
  if(bar){
    Foo<int> v;
  }
}

但似乎没有这样做。

(如果你真的想知道我为什么会这么想 - 它与this问题有关,而不是使用template class Foo<int>进行显式模板实例化我只是希望能够编写Foo<int> v,因为在很多情况下这更容易,因为它隐式实例化了所需的所有函数,并且在没有优化的情况下在调试模式下工作正常......)

更新

这是我想要做的(作为可编辑的迷你示例):

foo.h (这些文件是给我的,不可更改)

template<class T>
struct Foo {
  T val_;
  Foo(T val) : val_(val) {
      // heavy code, long compile times
  }
};

FOO-instantiation.cpp

#include "foo.h"
void neverCalled() {
  Foo<int> f(1);
}

// The standard way to instantiate it is this:
// template class Foo<int>;
// but in reality it is often hard to find out 
// exactly what types I have to declare.
// Usage like Foo<int> f(1); will instantiate all
// dependent types if necessary.

foo-decl.h (我从foo.h中提取的接口)

template<class T>
struct Foo {
  T val_;
  Foo(T val); // no heavy code, can include anywhere and compile fast
};

的main.cpp

#include <iostream>
#include "foo-decl.h"

int main(int argc, char** argv){
  Foo<int> foo(1);
  return 0;
}

编译(无优化)

g++ -c main.cpp
g++ -c foo-instantiation.cpp
g++ main.o foo-instantiation.oo

编译(优化)

g++ -O2 -c main.cpp
g++ -O2 -c foo-instantiation.cpp
g++ main.o foo-instantiation.oo
main.o(.text+0x13): In function `main':
: undefined reference to `Foo<int>::Foo(int)'
collect2: ld returned 1 exit status
  • 我尝试了预编译的头文件,但模板实例化方法可以更快地编译。
  • 在没有优化的情况下编译foo-instantiation.cpp并不理想,因为库代码(foo.h和其他代码)运行速度会慢一些。

7 个答案:

答案 0 :(得分:7)

您正在遇到一个定义规则。在一个文件中,您有一个定义:

template<class T>
struct Foo {
  T val_;
  Foo(T val) : val_(val) {
      // heavy code, long compile times
  }
};

和另一个不同的定义:

template<class T>
struct Foo {
  T val_;
  Foo(T val); // no heavy code, can include anywhere and compile fast
};

在C ++中明确不允许这样做(只允许一个相同的定义),如果你破坏了规则,你的代码有时似乎可以工作,但你实际拥有的是可怕的“未定义行为” - 任何事情都可能发生,取决于月亮的阶段(但更可能是某些关键时刻编译器的内部状态)。

基本上,你不能写那样的代码 - 抱歉。

答案 1 :(得分:4)

编译器无法优化函数体,无论是否声明 extern ,因为它无法知道该函数未从另一个编译单元调用。如果你声明它静态,它可以优化它,但我不相信任何编译器实际上都这样做。

编译器可以优化函数调用:

while(false) {
  foo();
}

在上面,可以省略对foo()的调用。

OTOH,链接器可以从最终的excecutable中删除函数体,如果它们没有被调用的话。

由于上述和其他原因,我们确实需要查看一些真实的代码才能诊断出您的问题。

答案 2 :(得分:2)

#pragma 主题下搜索文档。此定义是一种逃生舱口,允许您指定所有类型的属性。 gcc支持,所以有一个很好的赌注,即g ++也将如此。请注意,这些可能不适合您的项目可能会或可能不会引起关注。

答案 3 :(得分:2)

编译器正在优化一个从未使用过的变量,它不能以不使用的方式优化函数,因为它可以从不同的编译单元使用。您可以尝试强制编译器将变量视为类似于:

的变量
void instantiation()
{
   Foo<int> f;
   f; // mark the variable as if it is used.
}

// or:
Foo<int>* instantiation()
{
   Foo<int> *p = new Foo<int>();
   return p; // The compiler cannot know if p will be used, it must compile
}

更好的解决方案是在需要时显式实例化模板:

// .h
template <typename T>
class Foo
{
public:
   Foo( T const & value );
   void set( T const & ); // whatever else
private:
   T value_;
};

// template implementation another file, not included from .h
// instantiation.cpp??
template <typename T>
Foo<T>::Foo<T>( T const & value ) : value_(value) {}

template <typename T>
void Foo<T>::set( T const & v )
{
   value_ = value;
}

// explicit instantiation
template class Foo<int>;
template class Foo<double>;

// test.cpp
#include "header.h"
int main()
{
    Foo<int> f(5);
    f.set( 7 );

    Foo<char> f2; // linker error Foo<char>() not defined
}

用户代码只会看到标题并知道存在哪些方法,但不知道真正的实现。实现将在一个编译单元中编译,其中显式模板实例化发生。

请注意,如果您忘记显式实例化一个类型,那么它将是链接器错误,而不是编译错误。

单一定义规则

c ++中的一个定义规则规定每个符号或类只能有一个定义。对于常规符号,可以轻松检测到多个定义(如果定义两个void f() { },则链接器将检测重复的符号),但对模板来说有点棘手。使用模板时,它们通常在头文件中声明和定义,因此更加棘手。编译器在每个编译单元[1]中生成使用过的符号,链接器通常会找到多个等效符号(std :: vector :: push_back()被编译到每个具有std :: vector并调用push_back的编译单元中)

编译器将模板化代码标记为“弱”符号,表示虽然此处定义了符号,但它也可以在另一个编译单元中定义,并且链接器可以自由地丢弃符号而不会产生链接错误。如果要链接使用相同STL工具的不同编译单元(例如,使用相同类型),则需要这样做。

直到gcc 4.2,gcc linux链接器丢弃除了一个弱符号之外的所有符号而不进一步检查。一些链接器(linux中的gcc链接器将在不久的将来,而不是4.2,不知道4.3或4.4它可能在那里是alredy)确实检查不同的“弱”符号实际上是相同的并提供错误/警告给用户。

您的代码打破 ODR,因为您要在其他位置重新声明模板。您应该声明模板一次,并在外部实现上面公布的方法。无论如何,如果两个定义都是兼容的(因为它们在你发布的代码片段中):所有成员方法和属性完全相同并且具有相同的限定符(virtual / const-ness ...)它应该被编译器接受为那里只是模板的一个定义(重复)。

[1]只编译那些在代码中实际调用的方法:

template <typename T>
struct Test
{
   void f() { std::cout << "f()" << std::endl; }
   void g() { std::cout << "g()" << std::endl; }
};
int main()
{
   Test<int> t;
   t.f(); // compiler generates Test<int>::f, but not Test<int>::g
}

答案 4 :(得分:1)

这通常由编译器指令完成。在C中它将是一个#pragma,在Delphi Pascal中,它是{$ O-},{$ O +}围绕着有问题的代码。精确的语法和方法是特定于实现的,所以这是一个检查文档的问题,无论你使用什么系统。

不优化函数是非常简单的,但有一两次我看到有必要告诉编译器不要优化特定代码。这是非常罕见的,而不是我很长一段时间遇到的事情,但它偶尔会发生。它所做的事情通常是针对一些旧的遗留代码进行编译,这些遗留代码是在cpu技术的后期开发之前构建的 - 超线程是一个典型的例子。

答案 5 :(得分:0)

副手我不确定。也许Precompiled headers会解决这个问题吗?

只是稍微修改一下,显然这对于​​能够在代码中使用较小模板头的问题没有帮助,但它可能有助于编译时问题(从而消除了对模板的需求) )。

答案 6 :(得分:0)

将变量声明为volatile:

volatile Foo<int> v;

通常,它会阻止任何优化。我已经使用英特尔C ++编译器和Microsoft Visual Studio 2008进行了检查。

相关问题