Reputation: 81227
Related to, but somewhat different from, Do any compilers transfer effective type through memcpy/memmove
In C89, memcpy
and memmove
are required to behave as though the source and destination are accessed using character types, copying all the bits of the source to the destination without regard for the type of data being copied.
C99 changes the semantics so that if an object with an effective type is copied to storage which has no declared type [typically storage received from malloc or other such function], it creates an object in destination storage which may only be accessed using the source type.
The following code, for example, would have fully-defined behavior in C89 on all platforms where "unsigned int" and "unsigned long" have the same 32-bit representation, but would have Undefined Behavior under C99.
#include <stdio.h>
#include <string.h>
void increment_32_bit_uint(void *p)
{
uint32_t temp;
memcpy(&temp, p, sizeof temp);
temp++;
memcpy(p, &temp, sizeof temp);
}
int main(void)
{
unsigned *ip = malloc(sizeof (unsigned));
unsigned long *lp = malloc(sizeof (unsigned long));
*ip = 3; *lp = 6;
increment_32_bit_uint(ip);
increment_32_bit_uint(lp);
printf("%u %lu", *ip, *lp);
return 0;
}
Under the C99 rules, passing allocated storage to "increment_32_bit_uint" function will make it set the Effective Type to uint32_t, which cannot be the same type as both "unsigned" and "unsigned long" even if all three types have the same representation. Consequently, a compiler could do anything it likes with code that reads that storage as a type other than uint32_t, even if that type has the same representation.
Is there any way, in C99 or C11, of performing the copy in a way that would allow a compiler to generate efficient code, but would force the compiler to treat the destination as though it contained a pattern of bits with no effective type [which could thus be accessed using any type]?
Upvotes: 4
Views: 460
Reputation: 214395
You could get rid of all effective type problems if you just use a return type for the function.
uint32_t increment_32_bit_uint (const void* p)
{
u32_t result;
memcpy(&result, p, sizeof(result));
result++;
return result;
}
This will force the caller to be careful with their types. Though of course, in theory this is some sort of immutable object rather than an in-place change of the variable. In practice though, I think you'll get the most effective code from this anyway, if you use it as
x = increment_32_bit_uint(&x);
Generally, I don't see how any strict aliasing optimizations would ever be useful in real-world applications if they don't treat the stdint.h
types as compatible types to their primitive data type equivalents. Particularly, it must treat uint8_t
as a character type, or all professional low-level C code would break.
The same for your case here. If the compiler knows that unsigned int
is 32 bit, why would it decide to cause aliasing problems for users of uint32_t
and vice versa? That's how you turn a compiler useless.
Upvotes: 1