4 ms·
Interestingly (and pedantically), the code from the otherwise excellent "Byte order fallacy" article technically contains undefined behavior in C on machines wh
by moefh 8y ago
Interestingly (and pedantically), the code from the otherwise excellent "Byte order fallacy" article technically contains undefined behavior in C on machines where int is 32 bits wide (which is almost everything nowadays).
Consider this example of extracting a 32-bit int encoded as little endian:
#include <stdio.h>
int main(void)
{
unsigned char data[4] = { 0, 0, 0, 128 };
unsigned int i;
// the line of code from the article:
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
printf("%x\n", i);
}
Compiling this with either gcc or clang using "-fsanitize=undefined" triggers this runtime error:
test.c:9:61: runtime error: left shift of 128 by 24 places cannot be represented in type 'int'
That happens because, even though the bytes are unsigned as the article requires (using uint8_t instead of unsigned char has the same problem), they are promoted to (signed) int before the left shift as part of automatic integer promotion. When the left shift by 24 happens, it puts an 1 into the sign bit of the int, which is undefined behavior. Later the int is converted to unsigned (using implementation-defined behavior, which luckily is sane on all popular compilers), but by then it's already too late.
The solution is to change the code slightly:
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | ((unsigned)data[3]<<24);
This prevents the promotion from unsigned char (or uint8_t) to int by explicitly casting it to an unsigned int, which can then be safely shifted left by 24.
- ambrop7 8y agoYou are correct. I didn't want to go into these details to not confuse people even more :) But I think it is better to cast all the bytes to the final type (e.g. uint32) before shifting, not unknown types like unsigned int.
- d21d3q 8y agoCan't reproduce: https://onlinegdb.com/HyighU72Q https://onlinegdb.com/HyighU72Q
- moefh 8y agoI'm not familiar with that site, I don't see any way of passing arguments to the compiler. To reproduce you have to compile with "-fsanitize=undefined", as in: gcc -fsanitize=undefined -o test test.c ./test (clang also works with the exact same command line)