Reputation: 353
The general answer when asking "how does one implement memcpy function conformant with strict aliasing rules" is something along the lines of
void *memcpy(void *dest, const void *src, size_t n)
{
for (size_t i = 0; i < n; i++)
((char*)dest)[i] = ((const char*)src)[i];
return dest;
}
However, if I understand correctly, compiler is free to reorder call to memcpy and access to the dest, because it can reorder writes to char* with reads from any other pointer type (strict aliasing rules prevent only reordering of reads from char* with writes to any other pointer type).
Is this correct and if yes, are there any ways to correctly implement memcpy, or should we just rely on builtin memcpy?
Please note, that this question concerns not only memcpy but any deserialization/decoding function.
Upvotes: 11
Views: 2917
Reputation: 81227
If an object has no declared type, any effective type it may acquire will only be effective until the next time the object is modified. Writing to an object using a pointer of character type counts as modifying it, thus unsetting the old type, but writing it via character-type pointer does not set a new type unless such operation occurs as part of "copying as an array of character type", whatever that means. Objects which have no effective type may be legally read with any type.
Since the effective-type semantics for "copying as an array of character type" would be the same as those for memcpy
, a memcpy implementation could be written using character pointers for reading and writing. It may not set the effective type of the destination the way memcpy
would be allowed to, but any behavior which would be defined when using memcpy
would be defined identically if the destination were left with no effective type [as IMHO should have been the case with memcpy
].
I'm not sure who came up with the idea that a compiler can assume that storage which has acquired an effective type keeps that effective type when it is modified using a char*
, but nothing in the Standard justifies it. If you need your code to work with gcc, specify that it must be use with the -fno-strict-aliasing
flag unless or until gcc starts honoring the Standard. There's no reason to bend over backward trying to support a compiler whose authors continually seek out new cases to ignore aliasing even in cases where the Standard would require them to recognize it.
Upvotes: 0
Reputation: 214395
What everyone seems to be missing here, is that strict aliasing (6.5/7) depends on the term effective type (6.5/6). And effective type has explicit, special rules for the function memcpy
(6.5/6):
If a value is copied into an object having no declared type using
memcpy
ormemmove
, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.
So therefore I don't think it even makes sense to speak of strict aliasing inside the memcpy
function. You can only speak of strict aliasing if you know the effective type. Now, how do you determine that, based on the above? Is the internals of memcpy
a copy with memcpy
or not?
It's like saying "in order to understand which effective type that is used in memcpy, you must first understand which effective type that is used in memcpy".
So I don't quite see how the question, or any of the answers posted, make any sense.
Upvotes: 1
Reputation: 28902
The strict aliasing rule specifically excludes casts to char
types (see last bullet point below), so the compiler will do the correct thing in your case. Type punning is only a problem when converting things like int
to short
. Here the compiler may make assumptions that will cause undefined behavior.
C99 §6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- a character type.
Upvotes: 6
Reputation: 153977
Since both (char*)dest
and (char const*)src
point to char
, the compiler must assume that they might alias. Plus, there is a rule that says that a pointer to a character type can alias anything.
All of which is irrelevant for memcpy
, since the actual signature is:
void* memcpy( void* restrict dest, void* restrict src, size_t n );
which tells the compiler that there cannot be aliasing, because the user guarantees it. You cannot use memcpy
to copy overlapping areas without incurring undefined behavior.
At any rate, there's no problem with the given implementation.
Upvotes: 5
Reputation: 179991
Yes, you're missing something. The compiler may to reorder writes to dest
and reads to dest
. Now, since reads from src
happen-before writes to dest
, and your hypothethical read from dest
happens-after the write to dest
, it follows that the read from dest
happens-after the read from src
.
Upvotes: 0
Reputation: 14215
IANALL, but I don't think the compiler is allowed to mess things up in the way you describe. Strict aliasing is "implemented" in the spec by rendering undefined accesses to an object through an illegal pointer type, rather than by specifying another complicated partial order on object accesses.
Upvotes: 1