Search code examples
cmemorybit-manipulationbitwise-operatorsbit

How to save memory taking advantange of unused bits?


Take the hypotetical case that I have four unsigned char variables, and for whatever reason, in all of those variables, I never use the last bit, so I have four bits that I can use for another thing, without the need of having an extra variable.

e.g: example. How to use those bits that are highlighted in red.

I think that I will have to use math with bitwise operators, but I don't know if there is any right or wrong way.

Note: I'm trying to do this in C, so if you use C in the example, I'll be very grateful, but I think that other languages won't differ so much from the C approach.


Solution

  • In general C provides an abstract machine (to hide the underlying details) and converts your code into something that may be radically different. For the C abstract machine; an unsigned char has CHAR_BITS bits (maybe not 8 bits), structures (including bitfields) may have any amount of extra padding (whatever the compiler felt like), etc.

    If you care about the underlying details; then you have to break through the abstraction. In your case, this might mean (e.g.) using a uint32_t and doing shifting and masking yourself; so that you can have a guarantee that the compiler converts it into what you want.

    Example (untested and relatively awful - could use defines and macros):

    uint32_t demoPackedValue = ((uint32_t)'a' << 24) | ((uint32_t)'b' << 16) ((uint32_t)'c' << 8) | (uint32_t)'d';
    
    char getChar1(uint32_t packedValue) {
        return packedValue & 0x0000007FUL;
    }
    
    uint32_t setChar1(uint32_t packedValue, char c) {
        packedvalue = (packedvalue & 0xFFFFFF80UL) | ((uint32_t)c);
        return packedvalue;
    }
    
    int getBit1(uint32_t packedValue) {
        return !!(packedValue & 0x00000080UL);
    }
    
    uint32_t setBit1(uint32_t packedValue, int value) {
         if(value == 0) {
             packedValue &= 0xFFFFFF7FUL;
         } else {
             packedValue |= 0x00000080UL;
         }
        return packedvalue;
    }
    
    char getChar2(uint32_t packedValue) {
        return (packedValue & 0x00007F00UL) >> 8;
    }
    
    uint32_t setChar2(uint32_t packedValue, char c) {
        packedvalue = (packedvalue & 0xFFFF80FFUL) | ((uint32_t)c << 8);
        return packedvalue;
    }
    

    Of course that's also likely to harm performance (unless you have a very large amount of data and the extra cost of shifting/masking can be justified).