Bug 67773 - destructor called on temp object before and named object after move, while not being called on named object before move
Summary: destructor called on temp object before and named object after move, while no...
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.8.4
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-09-30 00:57 UTC by Adam Wenocur
Modified: 2015-09-30 13:49 UTC (History)
1 user (show)

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


Attachments
pre-processed C++11 file exhibiting this behavior (74.97 KB, text/plain)
2015-09-30 00:57 UTC, Adam Wenocur
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Adam Wenocur 2015-09-30 00:57:14 UTC
Created attachment 36420 [details]
pre-processed C++11 file exhibiting this behavior

The problem occurs on line 23813 of the provided .ii file.
An object initialized with specific parameters is dealloc'd once before moving, and once after moving, while the target of the move is never dealloc'd.

This demo is simplified from a program I'm writing that has to assign an object with an embedded pointer.  In the original, this object is destroyed twice separately, resulting in a double free on the pointer.  In the demo there is no pointer member, but the undesired behavior is the same.
The workaround featured in the demo is the same one that fixed the real program.

This problem appears to exist on dialects of C++, at least from '03 onward, but I chose to use C++11, because move constructors can be explicitly declared.  In C++03, it does the move in the same manner.

compilation command:
c++ -g -O0 -std=c++11 -save-temps -o test test.cpp

GCC info:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 

OS description:
Ubuntu 3.13.0-48.80-generic 3.13.11-ckt16
Comment 1 Adam Wenocur 2015-09-30 01:36:57 UTC
compiler output:

-*- mode: compilation; default-directory: "~/compiler-test/" -*-  
Compilation started at Wed Sep 30 00:45:55                        
                                                                  
./compile.sh                                                      
                                                                  
Compilation finished at Wed Sep 30 00:45:55


the command:
./test
Comment 2 Jonathan Wakely 2015-09-30 09:19:40 UTC
I don't see a bug here, could you please try a supported release and explain exactly what output you are seeing and what you expect to see.
Comment 3 Jonathan Wakely 2015-09-30 09:21:45 UTC
Oh, are you expecting this:

  a_thing = demo(demo::second);

to have the same behaviour as this:

  a_thing.~demo();
  new(&a_thing) demo(demo::second);

?

That's not how C++ works.
Comment 4 Adam Wenocur 2015-09-30 11:42:25 UTC
(In reply to Jonathan Wakely from comment #3)
> Oh, are you expecting this:
> 
>   a_thing = demo(demo::second);
> 
> to have the same behaviour as this:
> 
>   a_thing.~demo();
>   new(&a_thing) demo(demo::second);
> 
> ?
> 
> That's not how C++ works.

I don't expect the same behavior, however I expect the same end result:

for the destructor to be called on to be called on the named variable before the move, then for it to be called on the temporary object.
Comment 5 Adam Wenocur 2015-09-30 11:43:01 UTC
(In reply to Jonathan Wakely from comment #3)
> Oh, are you expecting this:
> 
>   a_thing = demo(demo::second);
> 
> to have the same behaviour as this:
> 
>   a_thing.~demo();
>   new(&a_thing) demo(demo::second);
> 
> ?
> 
> That's not how C++ works.

I don't expect the same behavior, however I expect the same end result:

for the destructor to be called on to be called on the named variable before the move, then for it to be called on the temporary object.
Comment 6 Adam Wenocur 2015-09-30 12:00:33 UTC
Whoops! Sorry about the malformed double-post.

Another way of explaining the problem is that the compiler appears to be suppressing the wrong destructor call.  Since this is a move and not a copy, or in C++03 it's a copy elision, it should be suppressing the destructor call on the temporary variable.  It's not doing this though; it's suppressing the first destructor call on the named variable instead.

When I get a chance, I'll build a GCC for testing purposes.  What version would be appropriate in this case?  Is 4.8.5 supported?
Comment 7 Jonathan Wakely 2015-09-30 13:45:32 UTC
(In reply to Adam Wenocur from comment #6)
> Another way of explaining the problem is that the compiler appears to be
> suppressing the wrong destructor call.  Since this is a move and not a copy,
> or in C++03 it's a copy elision,

No, you can't elide an assignment to an existing variable.

> it should be suppressing the destructor
> call on the temporary variable.  It's not doing this though; it's
> suppressing the first destructor call on the named variable instead.

No, that's not how C++ works.

> When I get a chance, I'll build a GCC for testing purposes.  What version
> would be appropriate in this case?  Is 4.8.5 supported?

No, the currently supported versions are listed on the home page, https://gcc.gnu.org/

The oldest supported release is 4.9.3
Comment 8 Jonathan Wakely 2015-09-30 13:49:41 UTC
Specifically, a move assignment doesn't destroy anything, it just performs an assignment. So the named variable is not destroyed, its move assignment operator is called, which does whatever it is written to do. The temporary is destroyed at the end of the expression, moving from it doesn't alter its lifetime in any way. The named variable is destroyed at the end of the block as usual, move assigning to it doesn't alter its lifetime in any way.

There are online compilers you can use to check the results from various GCC and Clang releases, e.g. http://melponlorg/wandbox

You will find none of them runs a destructor on the target of a move assignment, because that's not how C++ works.