3 ms·
> Mask, and then shift. > Repeat it like a mantra. You mask first, to define away potential concerns about signedness. Then you cast if needed. And finally, y
by trealira 2y ago
> Mask, and then shift.
> Repeat it like a mantra. You mask first, to define away potential concerns about signedness. Then you cast if needed. And finally, you can shift. Now we can create the fool-proof version for machines with at least 32-bit ints:
#define READ32BE(p) \
(uint32_t)(255 & p[0]) << 24 | (255 & p[1]) << 16 | (255 & p[2]) << 8 | (255 & p[3])
I don't think this works just because she's masking it. I'm pretty sure it's working because she cast (255 & p[0]) to uint32_t, and so all the other operands get promoted to uint32_t as well. I have this working with just casting to unsigned char first:
#include <stdio.h>
#include <stdint.h>
char b[4] = {0x02,0x03,0x04,0x80};
#define UC(x) ((unsigned char) (x))
#define READ32BE(p) (uint32_t)UC(p[3]) | UC(p[2]) << 8 | UC(p[1]) << 16 | UC(p[0]) << 24
int main(void) {
printf("%08x\n", READ32BE(b));
}
$ cc -fsanitize=undefined -g -Os -o /tmp/o endian.c && /tmp/o
02030480
Edit: actually, it works for me even without casting to uint32_t, without UBSan causing a runtime error, like in the article, so I don't know what's going on.
- moefh 2y ago> I don't think this works just because she's masking it. I'm pretty sure it's working because she cast (255 & p[0]) to uint32_t You're right, masking does nothing to solve the problem, what does it is the cast to uint32_t. Masking only helps if you're working with (signed) char, which is a bit silly. Your example works without the cast because the example in the article doesn't show the problem. That is, the following code doesn't have undefined behavior: char b[4] = {0x02,0x03,0x04,0x80}; #define READ32BE(p) p[3] | p[2]<<8 | p[1]<<16 | p[0]<<24 int main(int argc, char *argv[]) { printf("%08x\n", READ32BE(b)); } Note that b[0] (which is being shifted by 24) is 0x02, so everything works. To show the problem you'd need something like this (b[0] must be >= 128): char b[4] = {0x82,0x03,0x04,0x80};
- trealira 2y agoThanks for the explanation! That makes sense. I knew from reading K&R that, in converting a char to an int without sign extension, casting to unsigned char first is just as good as masking by 0xFF after, and I guess I was just not sufficiently familiar with the type promotion rules of C. This now causes a runtime error: #include <stdio.h> #include <stdint.h> char b[4] = {0x82,0x03,0x04,0x80}; #define UC(x) ((unsigned char) (x)) #define READ32BE(p) (uint32_t)UC(p[3]) | UC(p[2]) << 8 | UC(p[1]) << 16 | UC(p[0]) << 24 int main(void) { printf("%08x\n", READ32BE(b)); } ---- endian.c:8:22: runtime error: left shift of 130 by 24 places cannot be represented in type 'int' 82030480 But this works: #include <stdio.h> #include <stdint.h> char b[4] = {0x82,0x03,0x04,0x80}; #define CH_to_U32(x) ((uint32_t) (unsigned char) (x)) #define READ32BE(p) (CH_to_U32(p[3]) \ | CH_to_U32(p[2]) << 8 \ | CH_to_U32(p[1]) << 16 \ | CH_to_U32(p[0]) << 24) int main(void) { printf("%08x\n", READ32BE(b)); } ---- 82030480