C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Fixing std::bit_cast padding bit issues

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Fri, 16 Jan 2026 19:51:34 +0100
pt., 16 sty 2026 o 17:06 David Brown via Std-Proposals
<std-proposals_at_[hidden]> napisaƂ(a):
>
>
>
> On 16/01/2026 16:17, Jan Schultke via Std-Proposals wrote:
> >
> >
> > On Fri, 16 Jan 2026 at 15:55, Andrey Semashev via Std-Proposals
> > <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>>
> > wrote:
> >
> > On 16 Jan 2026 17:20, Jan Schultke via Std-Proposals wrote:
> > > > I think some kind of "pad to zero" concept could have uses
> > > beyond this
> > > > case, and be useful in its own right. You could have :
> > > >
> > > > T padded_to_zero(const T& x)
> > > >
> > > > that would return a copy of x, where the all padding
> > bits and
> > > bytes are
> > > > set to zero. And you could have :
> > > >
> > > > pad_to_zero(T& x)
> > > >
> > > > that would zero out the padding on an existing object.
> > > >
> > > >
> > > > That does not make any sense in the C++ object model.
> > Padding bits
> > > are
> > > > bits that are not in the value representation, and they
> > don't get
> > > > copied/preserved when values are copied.
> > >
> > > They are not copied or preserved with most copies, but they /are/
> > > accessible via things like memcpy and access using character or
> > > std::byte pointers. So sometimes they are relevant despite
> > not being
> > > part of the value of the object.
> > >
> > > You can copy them with memcpy (only at run-time), but examining the
> > > value results in undefined behavior.
> >
> > This should not be the case. I believe, the reason for it being an UB
> > was that integers used to allow trap values, which are no longer
> > allowed. Certainly, not for raw bytes.
> >
> >
> > The reason is also that granting the implementation the freedom to not
> > copy around padding bytes is an optimization opportunity,
>
> I think it is very rare in practice that padding bits or bytes will not
> be copied when the rest of a struct (or type with padding bits) is
> copied. Usually omitting those would be a pessimisation, not an
> optimisation.
>
> Consider the struct I had earlier - struct S { uint8_t a; uint32_t c; }.
> When copying this, a compiler will usually generate a single 64-bit
> move operation. Omitting the padding would mean an 8-bit move and a
> 32-bit move. And when the target is in a line of cache, it is more
> efficient for the entire cache line to be "dirty" than for parts of it
> to be untouched - those parts will need to be filled out with data
> fetched from the next level of cache or main memory. And for larger
> structures, copying padding can mean the copy can be a neat loop rather
> than individual instructions.
>
> There might be occasions when omitting the padding is more efficient,
> and of course when the compiler is free to copy them or not, it can do
> whatever is faster. But I suspect that this freedom will rarely give
> any significant efficiency benefits. (I am not suggesting changing the
> semantics here so that pads are always copied, merely saying that
> optimisation is not, IMHO, a big reason to avoid any specific changes.)
>
> > and relying on
> > the values of padding bytes is a massive (security-critical) bug anyway.
>
> I agree that /relying/ on the values of padding bits will be a bug
> (perhaps massive, perhaps not). But changing behaviour from "undefined"
> to "unspecified" is never going to introduce new bugs, and could easily
> reduce the consequences of existing bugs. However, it might make some
> bugs harder to detect with sanitizers - an advantage of UB is that
> sanitizers can complain about it. (I am, in general, a fan of the
> concept of UB - and the ability to spot errors with tools is one of my
> reasons.)
>

We could make that use of this bits could be erroneous behavior if
it's cast to unsigned int.
This means we do not have trap representation and the user can mask these bits.
but if someone tries to read these bits, the compiler can issue a warning.

> > There are still floating-point types where certain representations
> > result in CPU traps when used in an operation, if you find that to be
> > more convincing of a reason.
> >
> > memcpy has well defined behavior, regardless of how it's implemented. If
> > the program says memcpy then the source is copied as is to the target,
> > with padding bits and everything. I do not see a fundamental reason why
> > then the target should not be allowed to be used.
> >
> >
> > Because the source could also not be used. For all intents and purposes,
> > padding bits are eternally indeterminate quantum bits, and querying
> > their value results in your CPU exploding.
> >
> > And BTW, passing data via registers is not "doing god knows what", as
> > the data is stored in registers as is.
> >
> >
> > It is doing god knows what. When a type has four value bytes and four
> > padding bytes, moving it around may be done via 32-bit register move.
> > Whether that preserves the upper 32 bits or wipes them (like on x86) is
> > a CPU architecture implementation detail, and not anything you can
> > portably rely on.
>
> Sure. But at the hardware level, the bits are unspecified - they have a
> value, but you have no idea what that value is.
>
> As I understand it, the Itanium has an extra "valid" bit for each
> register - the idea is that reading a register with that bit cleared
> will give a trap or exception, helping you find "variable used before
> initialisation" bugs. Theoretically, such a system could also be used
> to track indeterminate bits. But I don't expect that to be a practical
> usage on real processors, and the possibility of such tracking should
> not be an argument against unspecified bits.
>
> >
> > Whether the compiler generatescode that does something other than
> > just moving the data, is irrelevant
> > because it is not allowed to modify the value representation as part
> > ofmemcpy, and padding bits in the source are unspecified anyway.
> >
> >
> > Padding bits are not part of the value representation, and the
> > implementation is free to only copy some of the bytes with memcpy
> > because there exists no way of observing the values of padding bytes
> > anyway. That is, under as-if rule optimization.
> >
> > > It's also reasonable to expect the compiler to spot a branch based on
> > > indeterminate bits within memcmp and optimize your code away
> > entirely.
> > > It's also reasonable to expect UBSan to terminate your program
> > when you
> > > memcmp a padded type, or for the compiler to treat such a call as
> > > equivalent to std::unreachable() and not even emit IR for it.
> >
> > You are talking about tools that implement the standard. The tools will
> > be updated once the standard changes, which is what we're discussing
> > here.
> >
> >
> > Yes, but it's entirely unrealistic to expect implementations to make
> > massive changes to how they handle padding bits, seeing that the current
> > model works reasonably well. To my understanding, LLVM does not even
> > have an object representation for objects during constant evaluation; it
> > is generated on the fly when you std::bit_cast.
> >
> > > There is even the problem of types like _BitInt(3) where the padding
> > > doesn't nicely bundle into whole bytes. It is unclear how those
> > could be
> > > handled by pad_to_zero, other than to bit-cast and zero the padding
> > > bits, which is exactly what I'm proposing.
> >
> > Padding bits are padding bits, whether they span the whole byte or not.
> > Thus, clear_padding/pad_to_zero would mask them to zero (e.g. with a
> > bitwise AND instruction).
> >
> >
> > That's just undefined behavior, and it probably should stay undefined
> > behavior.
> >
> > Internally, the result of reading indeterminate/padding bits may be an
> > LLVM poison value, and to my understanding, multiplying with zero or
> > doing a bitwise AND with zero does not "unpoison" values. That is what
> > you're conceptually asking for though.
> >
> > In general, I cannot empathize with this desire to manipulate and query
> > padding bits. The current object model treats them as almost not
> > existing, and it's a model that enables numerous optimizations and
> > allows for UBSan to protect people from their own stupidity and from
> > writing security-critical bugs.
> >
> >
>
> I would not want to be able to manipulate or query padding bits and
> bytes in general - if I want to do that for a type, I'll make the
> padding explicit (for user-defined types). But I do see it as a useful
> possibility when doing "low-level stuff" that is dealing with the
> underlying representation of data rather than the values of objects.
> That is what is going on when you are using bit_cast<>, memcpy,
> std::byte, or other ways of digging into how data is stored. I am not
> interested in the padding when doing "s1 = s2;" or using object values
> normally - this is "I know what I am doing, it's low-level and probably
> non-portable" stuff.
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2026-01-16 18:51:50