Bitmask for exactly one byte in C

Viewed 274

My goal is to save a long in four bytes like this:

unsigned char bytes[4];
unsigned long n = 123;

bytes[0] = (n >> 24) & 0xFF;
bytes[1] = (n >> 16) & 0xFF;
bytes[2] = (n >> 8) & 0xFF;
bytes[3] = n & 0xFF;

But I want the code to be portable, so I use CHAR_BIT from <limits.h>:

unsigned char bytes[4];
unsigned long n = 123;

bytes[0] = (n >> (CHAR_BIT * 3)) & 0xFF;
bytes[1] = (n >> (CHAR_BIT * 2)) & 0xFF;
bytes[2] = (n >> CHAR_BIT) & 0xFF;
bytes[3] = n & 0xFF;

The problem is that the bitmask 0xFF only accounts for eight bits, which is not necessarily equivalent to one byte. Is there a way to make the upper code completely portable for all platforms?

4 Answers

How about something like:

unsigned long mask = 1;
mask<<=CHAR_BIT;
mask-=1;

and then using this as the mask instead of 0xFF?

Test program:

#include <stdio.h>

int main() {
    #define MY_CHAR_BIT_8 8
    #define MY_CHAR_BIT_9 9
    #define MY_CHAR_BIT_10 10
    #define MY_CHAR_BIT_11 11
    #define MY_CHAR_BIT_12 12
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_8;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_9;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_10;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_11;
        mask-= 1;
        printf("%lx\n", mask);
    }
    {
        unsigned long mask = 1;
        mask<<=MY_CHAR_BIT_12;
        mask-= 1;
        printf("%lx\n", mask);
    }
}

Output:

ff
1ff
3ff
7ff
fff

I work almost exclusively with embedded systems where I rather often have to provide portable code between all manner of more or less exotic systems. Like writing code which will work both on some tiny 8 bit MCU and a x86_64.

But even for me, bothering with portability to exotic obsolete DSP systems and the like is a huge waste of time. These systems barely exist in the real world - why exactly do you need portability to them? Is there any other reason than "showing off" mostly useless language lawyer knowledge of C? In my experience, 99% of all such useless portability concerns boil down to programmers "showing off", rather than an actual requirement specification.

And even if you for some strange reason do need such portability, this task doesn't make any sense to begin with since neither char nor long are portable! If char is not 8 bits then what makes you think long is 4 bytes? It could be 2 bytes, it could be 8 bytes, or it could be something else.

If portability is an actual concern, then you must use stdint.h. Then if you truly must support exotic systems, you have to decide which ones. The only real-world computers I know of that actually do use different byte sizes are various obsolete exotic TI DSPs from the 1990s, which use 16 bit bytes/char. Lets assume this is your intended target which you have decided is important to support.

Lets also assume that a standard C compiler (ISO 9899) exists for that exotic target, which is highly unlikely. (More likely you'll get a poorly conforming, mostly broken legacy C90 thing... or even more likely those who use the target write everything in assembler.) In case of a standard C compiler, it will not implement uint8_t since it's not a mandatory type if the target doesn't support it. Only uint_least8_t and uint_fast8_t are mandatory.

Then you'd go about it like this:

#include <stdint.h>
#include <limits.h>
#if CHAR_BIT == 8
static void uint32_to_uint8 (uint8_t dst[4], uint32_t u32)
{
  dst[0] = (u32 >> 24) & 0xFF;
  dst[1] = (u32 >> 16) & 0xFF;
  dst[2] = (u32 >>  8) & 0xFF;
  dst[3] = (u32 >>  0) & 0xFF;
}
#endif 

// whatever other conversion functions you need:
static void uint32_to_uint16 (uint16_t dst[2], uint32_t u32){ ... }
static void uint64_to_uint16 (uint16_t dst[2], uint32_t u32){ ... }

The exotic DSP will then use the uint32_to_uint16 function. You could use the same compiler #if CHAR_BIT checks to do #define byte_to_word uint32_to_uint16 etc.

And then should also immediately notice that endianess will be the next major portability concern. I have no idea what endianess obsolete DSPs often use, but that's another question.

What about:

unsigned long mask = (unsigned char)-1;

This will work because the C standard says in 6.3.1.3p2

1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

And that unsigned long can represent all values of unsigned char.

#define CHARMASK ((1UL << CHAR_BIT) - 1)

int main(void)
{
    printf("0x%x\n", CHARMASK);
}

And the mask will always have width of the char. Calculated compile time, no additional variables needed.

Or

#define CHARMASK    ((unsigned char)(~0))

You can do it without the masks as well

void foo(unsigned int n, unsigned char *bytes)
{
    bytes[0] = ((n << (CHAR_BIT * 0)) >> (CHAR_BIT * 3));
    bytes[1] = ((n << (CHAR_BIT * 1)) >> (CHAR_BIT * 3));
    bytes[2] = ((n << (CHAR_BIT * 2)) >> (CHAR_BIT * 3));
    bytes[3] = ((n << (CHAR_BIT * 3)) >> (CHAR_BIT * 3));
}


int main(void)
{
    unsigned int z = 0xaabbccdd;
    unsigned char bytes[4];
    foo(z, bytes);
    printf("0x%02x 0x%02x 0x%02x 0x%02x\n", bytes[0], bytes[1], bytes[2], bytes[3]);
}
Related