Regarding your specific question:
The question is if this is legal and defined in C89?
the answer is no: it violates the strict aliasing rule. See What is the strict aliasing rule?. The compiler is free to do whatever it wants, including completely ignoring the statement:
*((uint32*)(&s.myVar_H)) = myValue;
A safer approach is to use memcpy
; your code snippet becomes:
MyStruct s;
memcpy(&s.myVar_H, &myValue, sizeof myValue);
Of course, in this particular instance, &s.myVar_H
is the same as &s
.
This assumes myValue
has to have the right size and format (e.g. it can't be a char
or a float
). It this is not the case, and if you have C99's compound literals, you can write:
memcpy(&s.myVar_H, (uint32[]){myValue}, sizeof(uint32));
This is ugly, but you can hide its ugliness in a macro:
#define LEGACY_CPY32(dst, src) memcpy(&(dst), (uint32[]){src}, sizeof(uint32))
But these suggestion will avoid only the strict-aliasing issue. Litte-endian problems and structure padding problems will still be present, and your code could break if you switch machines and/or compilers, or even just change compiler switches.
Update #1: Of course no one would ever write new code like this. But I am assuming the OP's goal is to update working, legacy code with the minimal number of changes, rather than doing a complete rewrite.
See also:
Update #2: Maybe I can present a more convincing case? For any construct, one can argue in terms of:
- legality
- efficiency
- elegance
For the third criteria, elegance, my suggestion loses hands down, compared with e.g. union punning.
For the second criteria, efficiency, John Regehr in Embedded in Academia, "Type Punning, Strict Aliasing, and Optimization" (June 2013) states regarding a similar issue:
Compilers such as GCC 4.8.1 and Clang ~3.3 (both for x86-64 Linux) fail to generate good code for c2
[an example of the type punning feature of unions] ... [and give] crappy object code
whereas in his example c5
which uses memcpy
:
Both compilers understand memcpy
deeply enough that we get the desired [optimal] object code.
In my opinion c5
is the easiest code to understand out of this little batch of functions because it doesn't do the messy shifting and also it is totally, completely, obviously free of complications that might arise from the confusing rules for unions and strict aliasing. It became my preferred idiom for type punning a few years ago when I discovered that compilers could see through the memcpy
and generate the right code.
Finally, let us look at the first criteria, legality. Regehr states regarding union punning (emphasis added):
Unfortunately this code [c2
, using union punning] is also undefined by modern C/C++ dialects (or maybe just unspecified, I'm not totally sure).
I am reluctant to disagree with Regehr, but the consensus on Stack Overflow seems to be that union punning has been valid since C99; see:
But it was not defined behavior in C89, which the OP specifically asks about.
Finally note the OP requests that:
Since this is an ancient legacy code based on a proprietary protocol I'd rather not touch the structures definition
The memcpy
solution leaves the header files untouched, which is advantageous if you are working with a team of touchy programmers. And you can easily back out all of your changes by redefining the macro LEGACY_CPY32
to be the previous "incorrect"-but-working functionality:
#define LEGACY_CPY32(dst, src) (*((uint32*)(&(dst))) = (src))