【问题标题】:Compile-time or Run-time evaluation of expression in constructor构造函数中表达式的编译时或运行时评估
【发布时间】:2015-11-10 20:17:50
【问题描述】:

我有以下课程:

template<ItType I, LockType L>
class ArcItBase;

使用(其中一个)构造函数:

ArcItBase ( StableRootedDigraph& g_, Node const n_ ) noexcept :
  srd ( g_ ), 
  arc ( I == ItType::in 
           ? srd.nodes [ n_ ].head_in 
             : srd.nodes [ n_ ].head_out ) { }

问题是(我不知道如何测试) arc 的构造函数的表达式的值是在编译时还是在运行时确定(发布、完全优化、clang-cl 和VC14),假设 I == ItType::in 可以在编译时评估(已知,IItType::inItType::out)为真或假?

【问题讨论】:

  • 您可以随时检查生成的汇编代码来检查这一点。
  • @matb 感谢您的关注,但是 27000 多行汇编程序(和引用的代码)对我来说似乎是在大海捞针...
  • 鉴于此构造函数中移动部件的数量,我认为您将不得不做很多工作才能在编译时对其进行评估。至少 ArcItBase 和 srd 的构造函数需要是 constexprsrdoperator[] 也是如此。
  • 您可以使用gcc.godbolt.org/#快速生成汇编代码。
  • 如果编译器可以在编译器中证明某些东西是常量,那么它可以将其优化为常量,请参阅this 示例。无论它是否会有所不同,您都必须查看组件。如果示例对于 Godbolt 来说太大了,那么编译器应该让您能够生成更具可读性的程序集,请参阅this for gcc examples, likely applicable to clang,这应该可以让您更有效地找到您的指针。

标签: c++


【解决方案1】:

在编译时不知道 ItType 的情况下无法编译您的代码。

模板参数在编译时求值,条件为核心常量表达式,标准参考为C++11 5.19/2。

在相反的情况下,编译器必须生成等效于

的代码
arc(true ? : )

如果你真的写它会被优化。然而,其余的条件不会被优化,因为您正在访问一个似乎是非静态成员并且不能作为核心常量表达式进行评估的东西。

然而,编译器可能并不总是像我们预期的那样工作,所以如果你真的想测试它,你应该转储反汇编的目标文件

objdump -DS file.o

然后您可以更好地浏览输出。

另一种选择是启动调试器并检查代码。

不要忘记,即使在优化的情况下,您也可以始终拥有符号,例如

g++ -O3 -g -c foo.cpp

您将在下面找到一个玩具实现。在第一种情况下,arcbase 的构造函数被赋值为:

arcbase<true> a(10,9);

而在第二个中,它被赋予在编译时无法知道的非常量随机值。

使用g++ --stc=c++11 -c -O3 -g 编译后,第一个案例创建:

Disassembly of section .text._ZN7arcbaseILb1EEC2Eii:

0000000000000000 <arcbase<true>::arcbase(int, int)>:
        srd isrd;

        arc iarc; 

        public:
        arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   c:   89 75 f4                mov    %esi,-0xc(%rbp)
   f:   89 55 f0                mov    %edx,-0x10(%rbp)
  12:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  16:   8b 55 f0                mov    -0x10(%rbp),%edx
  19:   8b 4d f4                mov    -0xc(%rbp),%ecx
  1c:   89 ce                   mov    %ecx,%esi
  1e:   48 89 c7                mov    %rax,%rdi
  21:   e8 00 00 00 00          callq  26 <arcbase<true>::arcbase(int, int)+0x26>
  26:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  2a:   8b 00                   mov    (%rax),%eax
  2c:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
  30:   48 83 c2 08             add    $0x8,%rdx
  34:   89 c6                   mov    %eax,%esi
  36:   48 89 d7                mov    %rdx,%rdi
  39:   e8 00 00 00 00          callq  3e <arcbase<true>::arcbase(int, int)+0x3e>
  3e:   c9                      leaveq 
  3f:   c3                      retq  

而第二种情况:

Disassembly of section .text._ZN7arcbaseILb1EEC2Eii:

0000000000000000 <arcbase<true>::arcbase(int, int)>:
        srd isrd;

        arc iarc; 

        public:
        arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}
   0:   53                      push   %rbx
   1:   48 89 fb                mov    %rdi,%rbx
   4:   e8 00 00 00 00          callq  9 <arcbase<true>::arcbase(int, int)+0x9>
   9:   48 8d 7b 08             lea    0x8(%rbx),%rdi
   d:   8b 33                   mov    (%rbx),%esi
   f:   5b                      pop    %rbx
  10:   e9 00 00 00 00          jmpq   15 <arcbase<true>::arcbase(int, int)+0x15>

查看反汇编,您应该注意到,即使在第一种情况下,值 10 也没有直接传递给构造函数,而是仅放置在从中检索 is 的寄存器中。

这是 gdb 的输出:

0x400910 <_ZN3arcC2Ei>                  mov    %esi,(%rdi)                                                      
0x400912 <_ZN3arcC2Ei+2>                retq                                                                    
0x400913                                nop                                                                     
0x400914                                nop                                                                     
0x400915                                nop                                                                     
0x400916                                nop                                                                     
0x400917                                nop                                                                     
0x400918                                nop                                                                     
0x400919                                nop                                                                     
0x40091a                                nop                                                                     
0x40091b                                nop                                                                     
0x40091c                                nop                                                                     
0x40091d                                nop                                                                     
0x40091e                                nop                                                                     
0x40091f                                nop                                                                     
0x400920 <_ZN7arcbaseILb1EEC2Eii>       push   %rbx                                                             
0x400921 <_ZN7arcbaseILb1EEC2Eii+1>     mov    %rdi,%rbx                                                        
0x400924 <_ZN7arcbaseILb1EEC2Eii+4>     callq  0x400900 <_ZN3srdC2Eii>                                          
0x400929 <_ZN7arcbaseILb1EEC2Eii+9>     lea    0x8(%rbx),%rdi                                                   
0x40092d <_ZN7arcbaseILb1EEC2Eii+13>    mov    (%rbx),%esi                                                      
0x40092f <_ZN7arcbaseILb1EEC2Eii+15>    pop    %rbx                                                             
0x400930 <_ZN7arcbaseILb1EEC2Eii+16>    jmpq   0x400910 <_ZN3arcC2Ei>

第二种情况的代码是:

struct llist
{
  int head_in;
  int head_out;

  llist(int a , int b ) : head_in(a), head_out(b) {}
};

struct srd
{
  llist nodes;
  srd(int a, int b) : nodes(a,b) {}
};

struct arc
{
  int y;
  arc( int x):y(x) {}
};


template< bool I > class arcbase
{
  srd isrd;

  arc iarc;

  public:
  arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}

  void print()
  {
    std::cout << iarc.y << std::endl;
  }

};


int main(void)
{

  std::srand(time(0));

  volatile int a_ = std::rand()%100;
  volatile int b_ = std::rand()%4;

  arcbase<true> a(a_,b_);

  a.print();

  return 0;
}

【讨论】:

  • !当您写道:“但是,条件的其余部分将不会被优化......”。您的意思是说srd.nodes [ n_ ].head_insrd.nodes [ n_ ].head_out 这两者之一将在编译时被“替换”,但它们本身不会被优化?
  • @degski srd.nodes 会发生什么取决于它们如何填充值。假设它们足够不复杂并且它们的值不依赖于运行时,那么它们将被优化。我将用一些新信息更新帖子以显示这一点。但是,除了上述内容之外,我不希望有任何优化。
  • 这些值取决于运行时。我没想到它们会被优化出来,只是选择了srd.nodes [ n_ ].head_insrd.nodes [ n_ ].head_out。感谢您所做的所有工作,我已经接受您的回答是正确的......
猜你喜欢
  • 2016-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-16
  • 2012-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多