1. Changelog
1.1. Revision 0 - February 12th, 2026
-
Initial release. ✨
2. Introduction and Motivation
We have personally claimed that type punning is a legal action in C. It is unambiguously NOT allowed in C++, but vendors fall on different sides of the divide when it comes to the behavior of type punning with a in C. That is, given the following asserts produce no constraint violations on a given implementation:
#include <stddef.h>#include <string.h>#include <stdalign.h>int main () { static_assert ( sizeof ( float ) == sizeof ( int ), "sizes are not similar" ); static_assert ( alignof ( float ) == alignof ( int ), "alignments are not similar" ); typedef union u { float f ; int i ; } u ; #if defined(UNNECESSARY_CHECK) && UNNECESSARY_CHECK == 1 // Not ncessary: both members start at an offset of `0` due to // §6.7.3.2 Structure and union specifiers, ❡18: // «A pointer to a union object, suitably converted, // points to each of its members (or if a member is a bit-field, // then to the unit in which it resides), // and vice versa. The members of a union object overlap in such a way // that pointers to them when // converted to pointers to character type point to the same byte.» static_assert ( offsetof ( u , f ) == offsetof ( u , i ), "offsets are not similar" ); #endif u pun = {. i = 0 }; const int nopun_i = 0 ; float nopun_f = 12334.0 ; memcpy ( & nopun_f , & nopun_i , sizeof ( int )); if ( memcmp ( & nopun_f , & pun . f , sizeof ( int )) == 0 ) { return 0 ; } return 1 ; }
(Godbolt). Is this program guaranteed to work and return ? Some implementers say no, and others say yes. This proposal provides 2 pieces of wording attempting to either formalize that wording officially in a "Semantics" section, or to move the type-punning to an explicit "Recommended practice" section so that the divergence is either harmonized in a single, unified direction or explicitly blessed.
NOTE: There was explicit divergence in implementer understanding. Whether or not they have leveraged this into actual implementation divergence or not is not something the author has looked into, but getting conflicting answers from e.g. two Clang developers or two GCC developers has not been very encouraging for the future.
3. Wording
The wording is against the latest working draft.
The intent is to provide 2 options.
-
OPTION 1: formalize the semantics of
type punning by moving the bulk of the footnote’s text into normative text and make the object representation identical to usingunion .memcpy -
OPTION 2: allow implementation divergence by placing the appropriate wording and examples in the recommended practice.
3.1. OPTION 1: Modify "§6.5.3.4 Structure and union members" to formalize union semantics
6.5.3.4 Structure and union membersConstraintsThe first operand of the
operator shall have an atomic, qualified, or unqualified complete structure or union type, and the second operand shall name a member of that type.. The first operand of the
operator shall be a pointer to an atomic, qualified, or unqualified complete structure or union type, and the second operand shall name a member of the type pointed to.-> SemanticsA postfix expression followed by the
operator and an identifier designates a member of a structure or union object. If the named member is a member of a union and it is not the member last stored into, then the value is the reinterpretation of the appropriate portion with the object representation (6.2.6) of the last stored value represented by the named member. Otherwise, the. Thevalue is that of the named member,94)and. The value is an lvalue if the first expression is an lvalue. If the first expression has qualified type, the result has the so-qualified version of the type of the designated member.EXAMPLE The process of reading members not explicitly written to in a union is sometimes called "type punning". This can possibly be a non-value representation. The following fragment returns
if no constraints are violated:0 #include <stddef.h>#include <string.h>int main () { static_assert ( sizeof ( float ) == sizeof ( int ), "sizes are not similar" ); static_assert ( alignof ( float ) == alignof ( int ), "alignments are not similar" ); typedef union u { float f ; int i ; } u ; u pun = {. i = 0 }; const int nopun_i = 0 ; float nopun_f = 12334.0 ; memcpy ( & nopun_f , & nopun_i , sizeof ( int )); if ( memcmp ( & nopun_f , & pun . f , sizeof ( int )) == 0 ) { return 0 ; } return 1 ; } A postfix expression followed by the -> operator and an identifier designates a member of a structure or union object. The value is that of the named member of the object to which the first expression points, and is an lvalue.95) If the first expression is a pointer to a qualified type, the result has the so-qualified version of the type of the designated member.
Accessing a member of an atomic structure or union object results in undefined behavior.96)
One special guarantee is made to simplify the use of unions: if a union contains several structures that share a common initial sequence (see following sentence), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.
94)If the member used to read the contents of a union object is not the same as the member last used to store a value in the object the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called type punning). This can possibly be a non-value representation.
3.2. OPTION 2: Modify "§6.5.3.4 Structure and union members" to make union semantics a recommended practice
6.5.3.4 Structure and union membersConstraintsThe first operand of the
operator shall have an atomic, qualified, or unqualified complete structure or union type, and the second operand shall name a member of that type.. The first operand of the
operator shall be a pointer to an atomic, qualified, or unqualified complete structure or union type, and the second operand shall name a member of the type pointed to.-> SemanticsA postfix expression followed by the
operator and an identifier designates a member of a structure or union object. The value is that of the named member. 94)and is an lvalue if the first expression is an lvalue. If the first expression has qualified type, the result has the so-qualified version of the type of the designated member.A postfix expression followed by the -> operator and an identifier designates a member of a structure or union object. The value is that of the named member of the object to which the first expression points, and is an lvalue.95) If the first expression is a pointer to a qualified type, the result has the so-qualified version of the type of the designated member.
Accessing a member of an atomic structure or union object results in undefined behavior.96)
One special guarantee is made to simplify the use of unions: if a union contains several structures that share a common initial sequence (see following sentence), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.
Recommended practiceIn the case a union member accessed with the
or. operators, if the named member is not the member last stored into, then the value is the reinterpretation of the appropriate portion with the object representation (6.2.6) of the last stored value represented by the named member. The process of reading members not explicitly or implicitly written to in a union is sometimes called "type punning".-> EXAMPLE Type punning can possibly be a non-value representation. The following fragment returnsif no constraints are violated:0 #include <stddef.h>#include <string.h>int main () { static_assert ( sizeof ( float ) == sizeof ( int ), "sizes are not similar" ); static_assert ( alignof ( float ) == alignof ( int ), "alignments are not similar" ); typedef union u { float f ; int i ; } u ; u pun = {. i = 0 }; const int nopun_i = 0 ; float nopun_f = 12334.0 ; memcpy ( & nopun_f , & nopun_i , sizeof ( int )); if ( memcmp ( & nopun_f , & pun . f , sizeof ( int )) == 0 ) { return 0 ; } return 1 ; } 94)If the member used to read the contents of a union object is not the same as the member last used to store a value in the object the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called type punning). This can possibly be a non-value representation.