There are many uses of reinterpret_cast
that will compile, but are UB. It's only well defined in a handful of cases. The ones that currently matter to me, are casting to:
- an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union)
- a char or unsigned char type
Say I have a buffer which contains a binary structure that I want to parse. I use a struct
which only contains char a[100]
and uses methods to, for example, extract the uint32_t
which resides at a[3]
. Is casting the buffer to the struct and then accessing the struct's array this way well defined?
If it is, I expect that's because of the two rules above; however, I'm also half-expecting that these rules don't apply to a char[]
, or that I may have alignment issues of some sort because it's an array and not just a char*
.
A small code sample to make the use case more clear:
struct Overlay {
char a[100];
uint32_t parseSomeField() const {
return my_char_to_uint32_t_function(&a[3]);
}
};
int main() {
std::vector<char> buffer(1024);
// fill buffer
auto overlay = reinterpret_cast<const Overlay*>(&buffer[0]);
std::cout << overlay->parseSomeField() << std::endl;
}
I assume replacing char a[100]
with simply char *a
would be OK for sure, but by giving Overlay
the size of the structure I want to parse, it allows me to do the following as well:
Overlay overlay;
// fill overlay by writing into it directly
std::cout << overlay.parseSomeField() << std::endl;
which saves some lines of code.
Edit:
Thanks to the answer and comments, it's become clear to me that this use of reinterpret_cast
is UB. The following supports both working with an existing buffer and copying into the struct directly. You can do sizeof
as well, which is nice. Also, this should be well defined:
struct VersatileOverlay {
char a[100];
static uint32_t parseSomeField(const char *a) {
return some_char_to_uint32_t_function(a + 3);
}
uint32_t parseSomeField() const {
return parseSomeField(&a[0]);
}
};
int main() {
std::vector<char> buffer(1024);
// fill buffer
std::cout << VersatileOverlay::parseSomeField(&buffer[0]) << std::endl;
VersatileOverlay vo;
memcpy(&vo, /*source ptr*/, sizeof(VersatileOverlay));
std::cout << vo.parseSomeField() << std::endl;
}
parseSomeField()
and its siblings will simply call their static counterparts, passing them the internal buffer.