Does the following code violate strict aliasing or otherwise result in undefined behaviour according to the C++11 standard? Are there better ways to achieve the same functionality?
void do_things(const std::array<char, 64> &block) {
// ...
}
int main() {
std::vector<char> buffer(64);
do_things(reinterpret_cast<const std::array<char, 64> &>(buffer[0]));
}
const char *
is a lot less painfuledit: since sizeof(std::array<char, n>)
isn't guaranteed to be equal to n
, I then propose the following:
void do_things(const char (&block)[64]) {
// ...
}
int main() {
std::vector<char> buffer(64);
do_things(reinterpret_cast<char (&)[64]>(buffer[0]));
}
According my understanding of aliasing this should not result in undefined behaviour and capture the semantics of passing an array of fixed size. Is my understanding correct?
The strict aliasing rule refers to §3.10 [basic.lval]/p10, which provides that
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
- the dynamic type of the object,
- a cv-qualified version of the dynamic type of the object,
- a type similar (as defined in 4.4) to the dynamic type of the object,
- a type that is the signed or unsigned type corresponding to the dynamic type of the object,
- a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
- an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union)
- [...]
Thus, accessing an object of type char
through a glvalue of type std::array<char, N>
does not break this rule, because std::array<char, N>
is an aggregate type that includes char
as an element of a nonstatic subaggregate data member.
However, you still can't do anything useful with the resulting reference without invoking undefined behavior because of a different rule - §9.3.1 [class.mfct.non-static]/p2:
If a non-static member function of a class
X
is called for an object that is not of typeX
, or of a type derived fromX
, the behavior is undefined.
It's also worth noting that no rule in the standard guarantees that sizeof(std::array<T, N>) == sizeof(T) * N
. The only things the standard guarantees is that std::array<T, N>
is an aggregate type and it can be initialized using a braced-init-list containing up to N
T
s. The implementation is free to add extra stuff.
Depending on what do_things
needs, you may want to make your function take random access iterators, or simply a pointer. Alternatively, if you want to limit your function to take only std::vector
and std::array
s, you can write overloads that take const refs to those and call a helper function taking const char *
that does the actual work.
The new version doesn't break any rule I can think of, but it's fairly bad design to require reinterpret_cast
to be used pretty much every time your function is called. If you accidentally declared buffer
as a std::vector<std::string>
, or wrote buffer
instead of buffer[0]
, the compiler will happily compile your code without a warning, with potentially disastrous results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With