Acceptable use of undefined behavior
Hello! I'm relatively new to C++ but a long time SWE, seeking some advice on pointer casting.
In the code below, I'm accepting some buffer (1-byte aligned) and swapping bytes as if it's an array of int16s. (Full code has equivalent methods for 32 and 64 bit types.)
Code:
#if defined(__GNUC__) || defined(__clang__)
#define BSWAP_INTRINSIC_2(x) __builtin_bswap16(x)
#elif defined(__linux__)
#include <byteswap.h>
#define BSWAP_INTRINSIC_2(x) bswap_16(x)
#elif defined(_MSC_VER)
#include <intrin.h>
#define BSWAP_INTRINSIC_2(x) _byteswap_ushort(x);
#else
#define BSWAP_INTRINSIC_2(x) (x << 8) | (x >> 8)
#endif
char* data; // some char buffer
// For debugging:
int alignment = reinterpret_cast<uintptr_t>(data) % alignof(uint16_t);
std::cout << "aligned? " << alignment << "\n";
uint16_t* data16 = reinterpret_cast<uint16_t*>(data);
for (size_t i = 0; i < length_of_data16; i++) {
data16[i] = BSWAP_INTRINSIC_2(data16[i]);
}
Sometimes, the `data16` pointer is not 2-byte aligned. Nonetheless, all of the possible definitions of BSWAP_INTRINSIC_2 work (i.e. MSVC, g++, clang; byteswap.h's bswap_16; and the bit-shifting fall through). In the assembly I see the compilers emitting MOVDQU, so they clearly identify that this might not be aligned and they're handling it.
- As I understand, it's technically illegal to cast a char* to a uint16_t* without verifying alignment. Is that true?
- Because this code works, how bad is it to use it?
- Is there a way to make this legal without using memcpy/memmove/etc. (memory impact is too high for big buffers) and while still making use of the fast SSE instructions that are currently being used (e.g. PSHUFB)?
Thanks!
Re: Acceptable use of undefined behavior
Quote:
Originally Posted by
zbjornson
As I understand, it's technically illegal to cast a char* to a uint16_t* without verifying alignment. Is that true?
more precisely, it's perfectly legal to reinterpret_cast pointers of basically any type ( ignoring cv qualification ), but doing basically anything with the result ( aside casting it back to the original type provided they have proper alignment ) will give undefined behaviour
Quote:
Originally Posted by
zbjornson
Because this code works, how bad is it to use it?
undefined behavoir is not "bad" per se, it just means that anything can happen ( including the program running as expected ) as far as the c++ abstract machine is concerned.
But, if *you*/the compiler/cpu/etc... can guarantee that some operation on some specific condition has some specific behavior then you're ok and nothing 'bad' will happen.
So, concerning your original issue, it depends on where the code will run on, consult your target compiler/cpu manual to know. AFAIR, for x86-32/64 the code should be ok at the cost of some performance penalty in the non aligned case ...
Re: Acceptable use of undefined behavior
Quote:
But, if *you*/the compiler/cpu/etc... can guarantee that some operation on some specific condition has some specific behavior then you're ok and nothing 'bad' will happen.
but note that behaviour that is not defined as part of the standard or not documented by the compiler supplier can change between compiler releases and should be tested with every compiler change.
Re: Acceptable use of undefined behavior
Quote:
Originally Posted by
2kaud
but note that behaviour that is not defined as part of the standard or not documented by the compiler supplier can change between compiler releases and should be tested with every compiler change.
indeed, a "not documented" behaviour does not look like a "guarantee" to me ;)
by "guarantee" I meant things like posix/winapi multithreading in pre-c++11, platform specific ABIs etc ...
Quote:
Originally Posted by
zbjornson
Is there a way to make this legal without using memcpy/memmove/etc. (memory impact is too high for big buffers) and while still making use of the fast SSE instructions that are currently being used (e.g. PSHUFB)?
BTW, I'm no SIMD expert, but given, say, a SIMD istruction operating on 16 bytes with a 2-byte UNaligned buffer, you could swap 14 bytes via that instruction and the remaining 2 bytes 'manually' ...
Re: Acceptable use of undefined behavior
Why take a chance? Wouldn't it be better to code something using 'defined' behavior so there is a reasonable chance that it will work continue to work in the future?
Re: Acceptable use of undefined behavior
Thanks all. Part of me was/is hoping that this in some way is not actually making use of UB and thus that it would be safe to do, since under the hood it's only doing byte-wise operations and not really interpreting int16s.
@superbonzo re: swapping 14 bytes on the alignment with SSE instructions and then 2 bytes, possible, although then I would have to write out the correct xmm2 (shuffle control mask) for each possible alignment and I couldn't make use of the simple built-ins. It would probably be easier to write out the assembly based on MOVDQU and PSHUFB :).
@Arjay entirely speed. It's sad to take a 6x performance hit to use defined behavior when the code works (with the three different compilers that the project is required to build with, even).