Hi!

C++ has
https://eel.is/c++draft/dcl.init#general-6.2
https://eel.is/c++draft/dcl.init#general-6.3
which says that during zero-initialization padding bits of structures
and unions are zero initialized, and in
https://eel.is/c++draft/dcl.init#general-9.3
says that in certain cases value-initialization is zero-initialization.
E.g. () initialization is value-initialization.

Now, looking at C23, it has something similar, called default
initialization, which also requires zeroing padding bits, unlike C++
in different cases.  Initialization with {} initializer new in C23 is
default-initialization, and if an initializer initializes some members
but not others, the rest are also default-initialized.  Reading C17,
that was apparently the case already there, eventhough it wasn't called
default initialization - "the remainder of the aggregate shall be initialized
implicitly the same as objects that have static storage duration" and
the static/thread_local storage duration initialization talking about the
clearing of padding bits.

Of course, if this is initialization of file/namespace scope or thread_local
variable, we do zero initialize padding bits forever.  A different thing are
automatic variable initializers.  For structures I think whether we clear
the padding bits or not is right now purely an optimization decision during
gimplification, whether we decide to optimize by clearing the whole
structure or not from performance POV.  And for unions we did clear the
padding (the whole union; the patch I've just posted changes that though).

And I think D has something similar.

My main question is when the standards say that the padding bits need to be
cleared, whether a valid program can actually observe that.

I'd hope that structure assignment is element-wise copying and so doesn't
need to preserve those bits.  What about memcpy, or *(unsigned char *),
or for C++ std::bit_cast inspection of the bits (constexpr for C++ or not)?

Or is the wording about clearing padding bits in both standards just useless
and it is still UB to depend on the value of the padding bits?

struct A { unsigned char a; unsigned int b; };
struct B { unsigned char c; struct A d; struct A e; unsigned long long f; };

void
foo (void)
{
#ifdef __cplusplus
  struct B a (); // value-initialization -> zero-initialization
  struct B b = { 0 }; // I think this is zero-initialization of d and e and f.
#else
  struct B a = {}; // New in C23, default-initialization of whole.
  struct B b = { 0 }; // I think this is default-initialization of d and e and 
f,
                      // even in C17 and possibly earlier.
#endif
  /* I think both standards say that the padding in between c and d here
     is cleared, can one inspect it like this?  Similarly padding between e and 
f.  */
  if (*((unsigned char *)&a + 1) != 0)
    abort ();
  /* My reading of either of the standards is that the padding in between
     b.c and b.d and b.e and b.f is uninitialized.  */
  /* My reading of either of the standards is that the padding in between
     b.d.a and b.d.b and padding between b.e.a and b.e.b is zero initialized
     (and similarly also for a.d.a and a.d.b etc.).  */
  if (*((unsigned char *)&b + offsetof (struct B, e) + 1) != 0)
    abort ();
}

If the padding bits need to be cleared and it needs to be observable for
C++ zero-initialization and for C default-initialization, I wonder if we
shouldn't introduce CONSTRUCTOR_CLEAR_PADDING flag, let the FEs set it on
CONSTRUCTORs where the standards guarantee clearing of padding bits and
during gimplification don't take it just as an optimization, but as a
conformance issue (say in the code in expr.cc I was touching inside of
CONSTRUCTOR_CLEAR_PADDING ctors never set *p_complete to -1, but set it to
0 in those cases instead to force the zeroing.  And a question is if we
should have some flag too for whether missing constructor elts result in
those elements to be effectively CONSTRUCTOR_CLEAR_PADDING or not (or
whether it is enough that we set *p_complete = 0 in that case anyway).

And another question is whether we want to keep such flag on the CONSTRUCTOR
preserved in GIMPLE, or whether we declare a mere struct whatever a = {};
in GIMPLE clears the padding too, or whether we use
MEM <char[sizeof (struct whatever)]> [&a, 0] = {};
for that.  And whether the DSE memory trimming shouldn't use some other
representation if the padding bits in it aren't needed to be cleared.
And whether we perform some optimizations which might break the padding
bits, SRA, or the DSE memory trimming (in case there is a store but some
padding bits left somewhere), etc.

Thoughts on this?

        Jakub

Reply via email to