Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Storing more bytes than a union member has, but less than the union size, with memcpy(3)
Parent
Storing more bytes than a union member has, but less than the union size, with memcpy(3)
Let's say we have an object, we store it in a union (into some other narrower type, but with memcpy(3), so it's allowed --I guess--), and then read it from the union via it's original type (so no alignment issues or anything.
$ cat union.c
#include <string.h>
struct s { int a; int b; };
struct t { int a; };
union u { struct s s; struct t t; };
int
main(void)
{
struct s x = {42, 53};
union u y;
int z;
memcpy(&y.t, &x, sizeof(x)); // y.t has declared/effective type of 'struct t'
z = y.s.b; // Is this UB?
return z;
}
I would guess the above is undefined behavior, exactly at the point of the read of y.s.b
.
The reason is that since we created an object of type struct t
via memcpy(3), then the compiler is free to assume that the object is no wider than sizeof(struct t)
, and so y.s.b
(which is beyond that) would be "uninitialized" (even though we really wrote bytes to it).
Is it UB as I expect?
However, neither GCC and Clang complain about such program:
$ gcc-13 -Wall -Wextra -Wpedantic -pedantic-errors union.c -O3 -fanalyzer -fsanitize=undefined -fsanitize=address
$ ./a.out; echo $?
53
$ clang-16 -Wall -Wextra -Wpedantic -pedantic-errors union.c -O3 -fsanitize=undefined -fsanitize=address
$ ./a.out; echo $?
53
BTW, does it change if I change and use allocated memory?
int
main(void)
{
struct s x = {42, 53};
union u *y = xmalloc(sizeof(union u)); // No declared/effective type
int z;
memcpy(&y->t, &x, sizeof(x)); // This sets the effective type to 'struct s'
z = y->s.b; // No UB?
return z;
}
Post
Reading from a union member who's size is larger than that of the last written member is explicitly allowed since C99, but the value of the extra bytes is unspecified. From the cppreference page on C unions:
If the size of the new type is larger than the size of the last-written type, the contents of the excess bytes are unspecified (and may be a trap representation). Before C99 TC3 (DR 283) this behaviour was undefined, but commonly implemented this way.
Since the value of the bytes is unspecified, the implementation may use any value in any instace, and does not need to document the behavior. That said, GCC does document this behavior (and clang tries to follow GCC's implementation defined behavior on Linux):
The relevant bytes of the representation of the [union] object are treated as an object of the type used for the access.
Also note that the implementation must not assume the destination of the memcpy is smaller than a union u
. Consider the definition of memcpy:
Copies count characters from the object pointed to by src to the object pointed to by dest. Both objects are interpreted as arrays of unsigned char.
Also consider that the object pointed to by dest is a union u
(whos size is the maximum size of all its members) not a struct t
, even though it may be treated as a struct t
in some cases.
0 comment threads