Bug 93916 - Implicit copy/assignment alters padding bits of storage
Summary: Implicit copy/assignment alters padding bits of storage
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-02-24 22:53 UTC by andysem
Modified: 2020-02-25 11:34 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2020-02-25 00:00:00


Attachments
A test case that demonstrates the problem (438 bytes, text/x-csrc)
2020-02-24 22:57 UTC, andysem
Details

Note You need to log in before you can comment on or make changes to this bug.
Description andysem 2020-02-24 22:53:27 UTC
This bug is related to bug #88101.

Consider this test case:

#include <cstdio>
#include <cstring>
#include <new>

struct struct_with_padding
{
    char x;
    short y;
};

template< typename To, typename From >
inline To bitwise_cast_clear_padding(From const& from) noexcept
{
    union cast_storage_t
    {
        unsigned char bytes[sizeof(To) < sizeof(From) ? sizeof(From) : sizeof(To)];
        From aligner;

        cast_storage_t() {}
    }
    storage;

    // Clear any possible internal padding bits. Placement new is required to perform member-wise initialization.
    std::memset(storage.bytes, 0, sizeof(storage.bytes));
    From* p = new (storage.bytes) From(from);

    To to;
    std::memcpy(&to, p, sizeof(to));

    return to;
}

int main()
{
    struct_with_padding a;
    std::memset(&a, 0xCC, sizeof(a));
    a.x = 1;
    a.y = 1;

    std::printf("%08x\n", bitwise_cast_clear_padding< unsigned int >(a));
}

Compile with: g++ -o clear_padding clear_padding.cpp

On x86-64 this code outputs 0001cc01, while the expected output is 00010001. The problem is that during the placement new the compiler apparently copies the padding bits from the `from` object to the new storage that was previously zero-initialized. According to N4849 [class.copy.ctor]/14, the implicitly generated copy constructor, which is called in this case, should initialize the new object member-wise. It doesn't say anything about modifying padding bits, and for trivially copyable types I would expect this to work.

This is important for Boost.Atomic implementation as it needs a way to initialize padding bits of user-defined structs in order for compare_exchange operations to work.

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.2.1-9ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)
Comment 1 andysem 2020-02-24 22:57:03 UTC
Created attachment 47902 [details]
A test case that demonstrates the problem
Comment 2 Andrew Pinski 2020-02-24 23:00:15 UTC
    std::memset(storage.bytes, 0, sizeof(storage.bytes));
    From* p = new (storage.bytes) From(from);

The memset here is considered as Dead code.
Comment 3 Richard Biener 2020-02-25 09:28:01 UTC
I think the behavior you see is perfectly valid.  Also Andrews comment is valid,
the memset is very likely elided.
Comment 4 andysem 2020-02-25 10:07:07 UTC
Are you saying that implementation is allowed to not preserve unused storage state upon construction and assignment? Because I don't think this is what the standard says.

Is there any other way to achieve the effect of initializing padding in a struct?
Comment 5 rguenther@suse.de 2020-02-25 10:52:03 UTC
On Tue, 25 Feb 2020, andysem at mail dot ru wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93916
> 
> --- Comment #4 from andysem at mail dot ru ---
> Are you saying that implementation is allowed to not preserve unused storage
> state upon construction and assignment? Because I don't think this is what the
> standard says.

I think the standard says that upon construction of an object the
previous memory state becomes indeterminate.

> Is there any other way to achieve the effect of initializing padding in a
> struct?

The only way I see would be to do that inside the constructor but I
realize that's not something you control?

I think the standard also gives you no way to access the padding
or guarantees anything about the paddings value which essentially
means you're relying on implementation behavior.
Comment 6 Jonathan Wakely 2020-02-25 10:57:29 UTC
(In reply to andysem from comment #0)
> It doesn't say anything about modifying padding bits,

It also doesn't say anything about leaving them with their previous values. I think your expectation is wrong.
Comment 7 Jonathan Wakely 2020-02-25 11:15:11 UTC
(In reply to andysem from comment #4)
> Are you saying that implementation is allowed to not preserve unused storage
> state upon construction and assignment? Because I don't think this is what
> the standard says.

I disagree. It doesn't say the values of padding bits are preserved after constructing an object in that storage.
 
> Is there any other way to achieve the effect of initializing padding in a
> struct?

The standard requires zero-initialization to clear all padding bits. Other forms of initialization (including copy-initialization as in your example) do not specify the effect on padding bits.
Comment 8 andysem 2020-02-25 11:26:16 UTC
(In reply to rguenther@suse.de from comment #5)
> > Is there any other way to achieve the effect of initializing padding in a
> > struct?
> 
> The only way I see would be to do that inside the constructor but I
> realize that's not something you control?

Exactly. Also, I don't think you can portably access padding bits, except to do memset over the object storage, like in my attempt. But then the constructor has to manually initialize all members. And that would make the class non-trivially copyable, which is not suitable in my case anyway.
Comment 9 andysem 2020-02-25 11:29:59 UTC
Ok, so it seems then that what I need cannot be implemented portably. In that case, this bug can be closed. Thanks to everyone.

But we do need a solution for bug #88101 (and Boost.Atomic) eventually.
Comment 10 Richard Biener 2020-02-25 11:34:38 UTC
Invalid.