Date: Sat, 17 Jan 2026 15:09:00 +0100
> So, to use your example with 80-bit long double on x86, where
> sizeof(long double) is 16, the behavior of memcpy must be equivalent to
> copying 16 bytes from the source to target, not 10. And this can be
> observed by a program such as this:
>
> bool test(const long double& d1)
> {
> unsigned char buf1[sizeof(long double)];
> memcpy(buf1, &d1, sizeof(long double));
> long double d2;
> memcpy(&d2, buf1, sizeof(long double));
> unsigned char buf2[sizeof(long double)];
> memcpy(buf2, &d2, sizeof(long double));
>
> return memcmp(buf1, buf2, sizeof(long double)) == 0;
> }
>
> This function must always return true, under the specification of memcpy
> and memcmp (which is also defined in C as operating on n characters).
> Whether the compiler generates code that does the actual copies and
> comparison of 16 bytes, or 10 bytes (for *both* copying and comparison)
> or none at all and just generates an equivalent of `return true` is QoI.
>
I think you're right regarding padding bits being observable, albeit only
at run-time. We have some wording that specifies values of padding bits
(e.g. zero-initialization says padding bits are zero), and so there are
some cases where padding bits are not indeterminate. I was operating under
the assumption that padding bits are indeterminate in all cases, but
they're not.
However, there is nothing that guarantees you that your test function is
well-defined. If d1 has dynamic storage duration or is marked
[[indeterminate]], padding bytes have indeterminate value, so you're just
reading uninitialized memory, and the function has undefined behavior. In
most other cases (e.g. local variables), d1 has padding bits with erroneous
value. This is a huge footgun, and encouraging users to modify and access
padding is exactly the wrong design direction.
Even in the few cases where padding bits have specified value, that value
is very easily lost through lvalue-to-rvalue conversion or any kind of
arithmetic operation.
> The proposed clear_padding utility would be defined as "sets all bits in
> the object representation that do not participate in value
> representation to zero" or something along these lines. This is a
> perfectly reasonable definition that is in line with the existing object
> model.
>
I have yet to hear any attempt at an argument for why we would want a
clear_padding utility rather than a bit_cast_zero_padding utility. As I've
said, padding bits are very easily lost. It also requires expert-level
knowledge to understand when padding bits have some specified value and
when they revert back to indeterminate or erroneous value. A clear_padding
utility also would not work for constant evaluation.
That being said, I'm not strongly opposed to making bit_cast preserve the
values of padding bits at run-time. However, that doesn't really solve the
problem that I set out to fix here. Those padding bits are almost certainly
going to be erroneous or indeterminate if the user doesn't use clear_padding,
and clear_padding doesn't really work during constant evaluation without
substantial changes to implementations.
The clear_padding + bit_cast idiom encourages the user to write the wrong
kind of code. If you forget clear_padding, you may get undefined behavior,
and what is currently a degenerate form of bit_cast that we could reject at
compile time (because it always produces UB) becomes undiagnosable; the
user could have run bit_cast expecting the padding bits to be copied, and
the compiler has no way of knowing whether that's the intent. Very sharp
edge!
> sizeof(long double) is 16, the behavior of memcpy must be equivalent to
> copying 16 bytes from the source to target, not 10. And this can be
> observed by a program such as this:
>
> bool test(const long double& d1)
> {
> unsigned char buf1[sizeof(long double)];
> memcpy(buf1, &d1, sizeof(long double));
> long double d2;
> memcpy(&d2, buf1, sizeof(long double));
> unsigned char buf2[sizeof(long double)];
> memcpy(buf2, &d2, sizeof(long double));
>
> return memcmp(buf1, buf2, sizeof(long double)) == 0;
> }
>
> This function must always return true, under the specification of memcpy
> and memcmp (which is also defined in C as operating on n characters).
> Whether the compiler generates code that does the actual copies and
> comparison of 16 bytes, or 10 bytes (for *both* copying and comparison)
> or none at all and just generates an equivalent of `return true` is QoI.
>
I think you're right regarding padding bits being observable, albeit only
at run-time. We have some wording that specifies values of padding bits
(e.g. zero-initialization says padding bits are zero), and so there are
some cases where padding bits are not indeterminate. I was operating under
the assumption that padding bits are indeterminate in all cases, but
they're not.
However, there is nothing that guarantees you that your test function is
well-defined. If d1 has dynamic storage duration or is marked
[[indeterminate]], padding bytes have indeterminate value, so you're just
reading uninitialized memory, and the function has undefined behavior. In
most other cases (e.g. local variables), d1 has padding bits with erroneous
value. This is a huge footgun, and encouraging users to modify and access
padding is exactly the wrong design direction.
Even in the few cases where padding bits have specified value, that value
is very easily lost through lvalue-to-rvalue conversion or any kind of
arithmetic operation.
> The proposed clear_padding utility would be defined as "sets all bits in
> the object representation that do not participate in value
> representation to zero" or something along these lines. This is a
> perfectly reasonable definition that is in line with the existing object
> model.
>
I have yet to hear any attempt at an argument for why we would want a
clear_padding utility rather than a bit_cast_zero_padding utility. As I've
said, padding bits are very easily lost. It also requires expert-level
knowledge to understand when padding bits have some specified value and
when they revert back to indeterminate or erroneous value. A clear_padding
utility also would not work for constant evaluation.
That being said, I'm not strongly opposed to making bit_cast preserve the
values of padding bits at run-time. However, that doesn't really solve the
problem that I set out to fix here. Those padding bits are almost certainly
going to be erroneous or indeterminate if the user doesn't use clear_padding,
and clear_padding doesn't really work during constant evaluation without
substantial changes to implementations.
The clear_padding + bit_cast idiom encourages the user to write the wrong
kind of code. If you forget clear_padding, you may get undefined behavior,
and what is currently a degenerate form of bit_cast that we could reject at
compile time (because it always produces UB) becomes undiagnosable; the
user could have run bit_cast expecting the padding bits to be copied, and
the compiler has no way of knowing whether that's the intent. Very sharp
edge!
Received on 2026-01-17 14:09:15
