直接析构函数调用已优化

-9 c++ gcc destructor

最近我在直接调用析构函数中发现了一个奇怪的行为,即值得一提的 IMU。

考虑下面的简单代码:

#include <iostream>

class S {
public:
   S() = default;
   ~S() { i=10; }
   int i{100};
};

int main()
{
   S s;
   do {
      std::cout << "Before foo: " << s.i;
      s.~S();
      std::cout << "; After: " << s.i << std::endl;
   } while (false);
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

跑步:

$g++ -O3 ./d.cpp -o d
$./d
Before foo: 100; After: 0
$g++ -O0 ./d.cpp -o d
$./d
Before foo: 100; After: 10
Run Code Online (Sandbox Code Playgroud)

IMU:这非常令人困惑。由于某种原因,对析构函数的直接调用似乎被优化掉了,即被忽略了。为什么?

我试图向 gcc 社区呼吁,但对他们的回复感到非常惊讶:gcc-bug-103843

他们无法理解案件,推诿和开玩笑,使环境适得其反,我不准备继续下去。

我做了一些额外的调查:

#include <iostream>

class S {
public:
   static constexpr size_t s_len=10;
public:
   S()  { for(auto i=0; i<s_len; ++i) m_arr[i]=10; }
   ~S() { for(auto i=0; i<s_len; ++i) m_arr[i]=11; /*prnt(); std::cout << std::endl;*/ }
   void prnt() { for(auto i=0; i<s_len; ++i) std::cout << m_arr[i] << "; "; }
public:
   int m_arr[s_len];
};

int main()
{
   S s;
   do {
      std::cout << "Before foo: "; s.prnt();
      s.~S();
      std::cout << "; After: ";    s.prnt();
      std::cout << std::endl;
   } while (false);
   return 0;
}

Run Code Online (Sandbox Code Playgroud)

跑步:

$g++ -O0 ./d2.cpp -o d2
$./d2 
Before foo: 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; ; After: 11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
$g++ -O3 ./d2.cpp -o d2
$./d2 
Before foo: 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; ; After: 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 
Run Code Online (Sandbox Code Playgroud)

这更令人困惑。

还有一项测试:

#include <iostream>

class S {
public:
   static constexpr size_t s_len=10;
public:
   S()  { for(auto i=0; i<s_len; ++i) m_arr[i]=10; }
   ~S() { for(auto i=0; i<s_len; ++i) m_arr[i]=11; prnt(); std::cout << std::endl; }
   void prnt() { for(auto i=0; i<s_len; ++i) std::cout << m_arr[i] << "; "; }
public:
   int m_arr[s_len];
};

int main()
{
   S s;
   do {
      std::cout << "Before foo: "; s.prnt();
      s.~S();
      std::cout << "; After: ";    s.prnt();
      std::cout << std::endl;
   } while (false);
   return 0;
}

Run Code Online (Sandbox Code Playgroud)

跑步:

$g++ -O0 ./d2.cpp -o d2
$./d2 
Before foo: 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
; After: 11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
$g++ -O3 ./d2.cpp -o d2
$./d2 
Before foo: 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
; After: 11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 
11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 

Run Code Online (Sandbox Code Playgroud)

这是完全正确的。

从我看来,直接调用析构函数并不简单,并且有一些协议。在相同情况下(-O3),它的行为有所不同,此外,它与一个版本的编译器不同。

我想更好地了解这些协议是什么?他们可靠吗?谁可以帮忙?

g++ --version
g++ (GCC) 10.2.1 20210130 (Red Hat 10.2.1-11)

Run Code Online (Sandbox Code Playgroud)

提前谢谢乔治

Ala*_*les 5

在对象被破坏后使用它是未定义的行为,因此编译器没有义务确保您的代码将按您的预期工作。可以自由地假设i析构函数运行后的值并不重要,因此它可以相应地进行优化,甚至可能优化器根本就不是为了处理这种情况而编写的,这就是为什么您会看到以下结果0:可能是一些默认值。同样,这对于编译器来说是完全合法的事情,因为您的代码的行为是未定义的,您很幸运它不会崩溃。

您可以看到,这可能就是在禁用优化时您的代码“有效”这一事实所发生的情况。