Bug 103843 - Direct call to Desctructor is optimized out
Summary: Direct call to Desctructor is optimized out
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 10.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-12-27 09:21 UTC by Georgii.Shagov
Modified: 2021-12-27 16:41 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Georgii.Shagov 2021-12-27 09:21:03 UTC
cat ./d.cpp
#include <iostream>

class S {
public:
   S() = default;
   ~S() { i=10; }
   void foo() { this->~S(); }
   int getI() const { return i; }
private:
   int i{0};
};

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

g++ -O0 ./d.cpp 
$./a.out 
Before foo: 0; After: 10

g++ -O3 ./d.cpp 
georgii@ltgscosvm:~/prj/test$./a.out 
Before foo: 0; After: 0

gcc --version
gcc (GCC) 10.2.1 20210130 (Red Hat 10.2.1-11)
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

hostnamectl
   Static hostname: ltgscosvm.be-tse01.de
         Icon name: computer-vm
           Chassis: vm
        Machine ID: fb944a0ffb46449f9b639e589d00b598
           Boot ID: 433e59a4db5c419a9081cc6968e4e590
    Virtualization: microsoft
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-1160.45.1.el7.x86_64
      Architecture: x86-64

uname -a
Linux ltgscosvm.be-tse01.de 3.10.0-1160.45.1.el7.x86_64 #1 SMP Wed Oct 13 17:20:51 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
Comment 1 Georgii.Shagov 2021-12-27 09:33:36 UTC
From experiments, I would guess that in case of -O3 the call to destructor was substituted by initializer:

This works fine:

cat ./d.cpp
#include <iostream>

class S {
public:
   S() : i{1} {}
   ~S() { i=0; }
   void foo() { this->~S(); }
   int getI() const { return i; }
private:
   int i{0};
};

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


g++ ./d.cpp 
$./a.out 
Before foo: 1; After: 0
$g++ -O3 ./d.cpp 
$./a.out 
Before foo: 1; After: 0
g++ --version
g++ (GCC) 10.2.1 20210130 (Red Hat 10.2.1-11)
Comment 2 Andrew Pinski 2021-12-27 09:38:50 UTC
This is undefined code. The object is officially not live after you call the deconstructor so GCC is able to remove the store from the deconstructor as being dead.
Comment 3 Georgii.Shagov 2021-12-27 09:44:58 UTC
(In reply to Andrew Pinski from comment #2)
> This is undefined code. The object is officially not live after you call the
> deconstructor so GCC is able to remove the store from the deconstructor as
> being dead.

I appreciate your reply. But this is confusing. The object was NOT released (de-allocated). In essence Destructor is just a function. Why the content of the class had been re-initialized?
IMU: there should be not such obvious difference between optimized and non-optimized code

thnx in advance.
Comment 4 Andrew Pinski 2021-12-27 09:51:08 UTC
(In reply to Georgii.Shagov from comment #3)
> (In reply to Andrew Pinski from comment #2)
> > This is undefined code. The object is officially not live after you call the
> > deconstructor so GCC is able to remove the store from the deconstructor as
> > being dead.
> 
> I appreciate your reply. But this is confusing. The object was NOT released
> (de-allocated). In essence Destructor is just a function. 

No, the destructor is not just a function in C++, it terminates the object being alive (not de-allocated though). You can then reuse the space for another object, either the same type or a different type by using the inplacement new.

> Why the content of the class had been re-initialized?

It was not re-initialized rather the store was removed.

> IMU: there should be not such obvious difference between optimized and
> non-optimized code

why not, it is undefined code.
Comment 5 Georgii.Shagov 2021-12-27 12:03:20 UTC
> No, the destructor is not just a function in C++, it terminates the object
> being alive (not de-allocated though). You can then reuse the space for
> another object, either the same type or a different type by using the
> inplacement new.
> 
> > Why the content of the class had been re-initialized?
> 
> It was not re-initialized rather the store was removed.
> 
> > IMU: there should be not such obvious difference between optimized and
> > non-optimized code
> 
> why not, it is undefined code.

I really appreciate and value your Reply, Andrew.

I have modified the code a little bit:

#include <iostream>

class S {
public:
   S() = default;
   ~S() { i=10; }
   void foo() { this->~S(); }
   int getI() const { return i; }
private:
   int i{100};
};

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

$g++ -O3 ./d.cpp 
$./a.out 
Before foo: 100; After: 0
$g++ -O0 ./d.cpp 
$./a.out 
Before foo: 100; After: 10

> No, the destructor is not just a function in C++, it terminates the object
> being alive (not de-allocated though). 

I do understand your point, yet I would not feel comfortable in sharing one.

- It is not clear: what function/code has reset S::i value to 0 after a Direct call to Destructor?
- Why 0? :-?
- In order to avoid misleading I have allocated the object on the stack as you can see. So, no-memory mgmt is not involved
- Does it mean gcc implants some code, being called after Destructor call, resetting the content of the class Instance to 0? ::memset??
- Is there any agreement unto this? I would be happy to learn more about this.


Thnx in advance,
Yours truly, George
Comment 6 Jakub Jelinek 2021-12-27 12:08:25 UTC
Accessing the member after the containing variable has been destructed is undefined behavior in C++, so when you do it, anything can happen, it can format your disk, do nothing, crash, ...
Comment 7 Jonathan Wakely 2021-12-27 16:38:03 UTC
Also, this is an automatic (i.e. stack) variable, so the destructor will run at the end of the block, meaning it will be settled twice. So another form of undefined behaviour. The program is just broken, in multiple ways. You cannot expect sensible behaviour from garbage code.
Comment 8 Jonathan Wakely 2021-12-27 16:41:37 UTC
(In reply to Jonathan Wakely from comment #7)
> Also, this is an automatic (i.e. stack) variable, so the destructor will run
> at the end of the block, meaning it will be settled twice. 

Sorry, auto-correct changed "destroyed" to "settled"