Reputation: 21
Lets have following function:
int encode(uint8b *dest, MyType srcType, const void *src)
{
uint32b value = 0;
uint64b value64 = 0;
switch (srcType)
{
case MyType_Usint: value = (uint32b)*(uint8b*)src; break;
case MyType_Uint: value = (uint32b)*(uint16b*)src; break;
case MyType_Udint: value = *(uint32b*)src; break;
case MyType_Ulint: value64 = *(uint64b*)src; break;
}
// now encode value to dest
}
I have passed wrongly aligned uint8b Data[sizeof(uint64b)]
as src
, I will fix the alignment.
But I received a demand to cast Data
when calling the function to proper type, i.e. encode(dest, MyType_Uint, (uint16b*)Data)
, which I think would cause in some more annoying unnecessary switches.
It's working even with the wrong alignment on platforms accessible for me, but I'm not sure, how it affects the other.
Would such cast fix alignment and/or endianess?
And yes, I really need the void*
parameter.
Upvotes: 2
Views: 904
Reputation: 238311
encode(dest, MyType_Uint, (uint16b*)Data)
Would such cast fix alignment and/or endianess?
Such cast does not fix alignment nor endiannes.
The way to call that function without undefined behaviour is following:
uint16b u = some_value;
encode(dest, MyType_Uint, &u);
uint64b ul = some_other_value;
encode(dest, MyType_Ulint, &ul);
Does such cast actually do anything?
Such cast changes the type of the expression. In this case, the C style explicit conversion does reinterpret cast.
The converted pointer can be used only in limited ways. You can convert it back to the original type (uint8b*
in your case). Indirection through the pointer is UB in most cases, with few exceptions, which include pointer-interconvertible objects, as well as using a pointer to a narrow character type as the result of the conversion. There is no exception that would apply to your example, so it would have UB.
Note that for some pointers, the C style explicit conversion does static cast, rather than reinterpret cast. For example, when the pointers are to classes in the same inheritance hierarchy. This is why C style casts are to be avoided: Use the cast that you intend to use.
Upvotes: 3
Reputation: 3584
This code is unlikely to work on platform that can't do unaligned memory access (like ARM9/ARM64 etc...).
This is because when you do value64 = *(uint64b*)src
you expect the CPU to access a 8 bytes word (and as such it must be aligned to 64bit address) and the signature of the function does not guaranty this.
Typically, on such platform, calling:
char s;
int a = encode(dest, MyType_Ulint, &s);
will compile, and will crash at runtime (but will run ok on x86/amd64 system, yet with undefined behavior).
If you want something portable, you should do something like this:
enum MyType
{
MyType_Usint,
MyType_Uint,
MyType_Udint,
MyType_Ulint
};
int encode(uint8b *dest, MyType srcType, const void *src)
{
uint32b value = 0;
uint64b value64 = 0; // This is guaranted to be aligned for 64bit access
size_t expected_operand_size[] = { 1, 2, 4, 8 };
memcpy(&value64, src, expected_operand_size[(int)srcType]);
switch (srcType)
{
case MyType_Usint: value = *(uint8b*)&value64; break;
case MyType_Uint: value = *(uint16b*)&value64; break;
case MyType_Udint: value = *(uint32b*)&value64; break;
case MyType_Ulint: break;
}
// now encode value to dest
}
By the way, you should use template code here for convenience and to avoid the (useless) copy and be easier to understand (no need to have an type enum) :
template <typename T>
struct Encode
{
static uint64b convert(const void * src) { T t; memcpy(&t, src, sizeof(t)); return t; }
static uint64b convert(const T * src) { return *src; }
};
// Specialisation when we can avoid the copy
template <>
struct Encode<uint8b>
{
static uint64b convert(const void * src) { return (uint8b)*src; }
static uint64b convert(const uint8b * src) { return *src; }
};
template <typename T>
int encode(uint8b * dest, const void* src)
{
uint64b value64 = Encode<T>::convert(src);
// your encoding code here
}
// Use like this:
void * s = ...;
uint16b * s2 = ...;
uint32b * s3 = ...;
encode<uint8b>(dest, s); // No copy, calls Encode<uint32b>::do(const void*)
encode<uint16b>(dest, s2); // No copy, calls Encode<uint16b>::do(const uint16b*)
encode<uint32b>(dest, s3); // No copy, calls Encode<uint32b>::do(const uint32b*)
// or this is also safe
encode<uint16b>(dest, s); // Copy here for fixing alignment
Upvotes: 1
Reputation: 148880
Casting a pointer never fix alignment nor endianness. It just re-interpret the address as pointing to a different type, and standard does not allow to de-reference it unless an object of the appropriate type lies at that address.
The conformant way to use a possibly misaligned representation is to use memcpy
:
int encode(uint8b *dest, MyType srcType, const void *src)
{
uint32b value = 0;
uint64b value64 = 0;
switch (srcType)
{
// any object can be accessed through a char pointer
case MyType_Usint: uint8b tmp8 = *(uint8b*)src; value = tmp8; break;
// use a temporary for 16 bits type (endianness question)
case MyType_Uint: uint16b tmp16; memcpy(&tmp16, src, sizeof(tmp16));
value = tmp16; break;
// directly memcpy into the value when size is the same
case MyType_Udint: memcpy(&value, src, sizeof(value)); break;
case MyType_Ulint: memcpy(&value64, src, sizeof(value64)); break;
}
// now encode value to dest
}
Intel type (80x86 to core) are known to be tolerant to misalignment, but other processors are not and raise an error if you attempt a misaligned access.
Upvotes: 2