Search code examples
cstructprintftypedef

How to create 24 bit unsigned integer in C


I am working on an embedded application where RAM is extremely tight. For this purpose I need to create a 24 bit unsigned integer data type. I am doing this using a struct:

typedef struct
{
    uint32_t v : 24;
} uint24_t;

However when I interrogate the size of a variable of this type, it returns "4", i.e.:

    uint24_t x;
    x.v = 0;
    printf("Size = %u", sizeof(x));

Is there a way I can force this variable to have 3 bytes?

Initially I thought it was because it is forcing datatypes to be word aligned, but I can for example do this:

typedef struct
{
    uint8_t blah[3];
} mytype;

And in that case the size comes out at 3.


Solution

  • Well, you could try to ensure that the structure only takes up the space you need, with something like:

    #pragma pack(push, 1)
    typedef struct { uint8_t byt[3]; } UInt24;
    #pragma pack(pop)
    

    You may have to provide those compiler directives (like the #pragma lines above) to ensure there's no padding but this will probably be the default for a structure with only eight-bit fields(a).

    You would probably then have to pack/unpack real values to and from the structure, something like:

    // Inline suggestion used to (hopefully) reduce overhead.
    
    inline uint32_t unpack(UInt24 x) {
        uint32_t retVal = x.byt[0];
        retVal = retVal << 8 | x.byt[1];
        retVal = retVal << 8 | x.byt[2];
        return retVal;
    }
                                          
    inline UInt24 pack(uint32_t x) {
        UInt24 retVal;
        retVal.byt[0] = (x >> 16) & 0xff;
        retVal.byt[1] = (x >> 8) & 0xff;
        retVal.byt[2] = x & 0xff;
        return retVal;
    }
    

    Note that this gives you big-endian values regardless of your actual architecture. This won't matter if you're exclusively packing and unpacking yourself, but it may be an issue if you want to use the memory blocks elsewhere in a specific layout (in which case you can just change the pack/unpack code to use the desired format).

    This method adds a little code to your system (and a probably minimal performance penalty) so you'll have to decide if that's worth the saving in data space used.


    (a) For example, both gcc 7.3 and clang 6.0 show 3 6 for the following program, showing that there is no padding either within or following the structure:

    #include <stdio.h>
    #include <stdint.h>
    
    typedef struct { uint8_t byt[3]; } UInt24;
    int main() {
        UInt24 x, y[2];
        printf("%zd %zd\n", sizeof(x), sizeof(y));
        return 0;
    }
    

    However, that is just a sample so you may want to consider, in the interest of portable code, using something like #pragma pack(1), or putting in code to catch environments where this may not be the case.