1

I don't understand why the padding after m_data, when the copy constructor constructs the object, is not initialized to 0 in an optimized build like -O2. The default constructor always seems to initialize it. I haven't tried yet other special member functions.

Is this behaviour implementation-defined? Not defined?

Is there a way to initialize it?

Compiler Explorer

Thanks

#include <climits>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <span>

void print(std::span<const std::byte> const span)
{
  for (auto const i : span) {
    std::cout << std::to_integer<int>(i) << ' ';
  }
  std::cout << '\n';
}

struct Foo {
  Foo()
  {}
  Foo(const Foo&)
  {}
  /*alignas(1)*/ std::byte m_data[1] = {};
  uint32_t val = INT_MAX;
};

inline auto objAsBytes(const auto& obj)
{
  return std::as_bytes(std::span { std::addressof(obj), 1 });
}

int main()
{
  auto foo = Foo {};
  std::span fooAsBytes = objAsBytes(foo);
  std::cout << "fooAsBytes\n";
  print(fooAsBytes);

  auto fooCopy = foo;
  std::span fooCopyBytes = objAsBytes(fooCopy);
  std::cout << "fooCopyBytes\n";
  print(fooCopyBytes);

  Foo fooAssign;
  fooAssign = foo;
  std::span fooAssignBytes = objAsBytes(fooAssign);
  std::cout << "fooAssignBytes\n";
  print(fooAssignBytes);
}
monamimani
  • 273
  • 2
  • 11
  • 3
    Your need to initialise padding seems to indicate that you are off the normal roads. Please explain why this is important to you. Consider whether this might be a https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Yunnosch May 10 '23 at 06:15
  • 2
    `I don't understand why the padding after m_data, [...], is not initialized to 0` Because it costs cycles and doesn't do anything. `The default constructor always seems to initialize it.` It doesn't. Check the assembly. – tkausl May 10 '23 at 06:20
  • 1
    The values of padding bits are not defined. If you need them to be defined, place an object in there, by `std::byte m_data[4]` – chrysante May 10 '23 at 06:20
  • 2
    I suspect the reason that padding bytes are not initialized (beyond the trivial "because the spec doesn't say they have to be") is because initializing the padding bytes would take more CPU cycles than not initializing them, and the C++ spec tries to avoid mandating the unnecessary expenditure of CPU cycles whenever it can. But it shouldn't matter if they are uninitialized, since your code should never be reading from them anyway. – Jeremy Friesner May 10 '23 at 06:22
  • I agree that the most plausible reason is to save cycles, but from compiler explorer execution, I see ( in GCC) that in optimized build O2 they are initialized, which goes against this reasoning. Plus, the default constructor initializes them. This behaviour seems to be consistent between msvc, GCC and clang. – monamimani May 10 '23 at 06:27
  • @Yunnosch, I want to understand why the compilers behave like this. I saw a discrepancy that I couldn't explain. I'd like to explain it. – monamimani May 10 '23 at 06:29
  • 4
    Equally with it being faster to not implement the padding it could also be faster to initialise them, e.g. the compiler could decide to initialise your class with a single 64-bit store rather than two 8 and 32 bit stores. It's only padding, it's value doesn't matter so the compiler can do whatever it likes to it – Alan Birtles May 10 '23 at 06:39
  • 2
    "I want to understand why the compilers behave like this." Because they are allowed to and if they see a chance to optimise they will and different compilers (or creators of compilers) will see different chances to opiptimise. Usually with good reason, sometimes maybe by misunderstanding. I do not think that this really is the core of what you want to know. – Yunnosch May 10 '23 at 07:28
  • I don't think the reason is to save cycles. Initializing an instance in memory involves an 8-byte range. But [there is no instruction that would be able to write a 64-bit immediate to memory in x64 ISA](https://stackoverflow.com/q/62771323/580083). For this reason, two stores are required. Whether the first store will be 8-bit (padding not involved) or 32-bit (padding involved) has likely no impact on cycles. But the first one has a shorter opcode, which I guess is the reason why it was chosen by GCC: https://godbolt.org/z/86GGq5zdK (note that there is no difference between both constructors). – Daniel Langr May 10 '23 at 08:05
  • It doesn't matter what the reason is. The compiler isn't obliged to emit any code to implement this non-feature, so it doesn't. Saves both space and time, but in any case why *should*& it emit any code? – user207421 May 10 '23 at 12:12
  • 1
    @JeremyFriesner Not only would initializing the padding bits use more cycles; it may also be a very inefficient memory access because it is unaligned. The padding bits are introduced to make access efficient, and *then* initializing them anyway is anathema. (That said, of course intrinsic operations may make it fast on some architectures to zero an entire memory range, as Alan correctly says, sure; but the compiler writers have only so much time to write code handling niches and special cases, so the compiler may just do what is always pretty good and never catastrophic.) – Peter - Reinstate Monica May 11 '23 at 06:53

1 Answers1

1

After more research today, I found a good explanation.

First, I found a good related question, Does C++ standard guarantee the initialization of padding bytes to zero for non-static aggregate objects?

It first mentions cppreference zero-initialization,

c++ standard dcl.init.general#6 which states this:

if T is a (possibly cv-qualified) non-union class type, its padding bits ([basic.types.general]) are initialized to zero bits and each non-static data member, each non-virtual base class subobject, and, if the object is not a base class subobject, each virtual base class subobject is zero-initialized;

The question also mentions differences between GCC and Clang regarding zero-initialization. It is not my question but good to mention.

The reason zero-initialization is not happening is a mix of having user-defined default and copy constructors and default member initializers instead of value initialization.

monamimani
  • 273
  • 2
  • 11