This is begging for an interface, either with functions or macros, something like:
// Use unsigned ints (assuming that's your 32-bit type).
#define setLast(x) (x) |= 2
#define clrLast(x) (x) &= ~2
#define isLast(x) ((x) & 2)
#define setUsed(x) (x) |= 1
#define clrused(x) (x) &= ~1
#define isUsed(x) ((x) & 1)
You can also provide macros to extract the size portion and create the whole integer:
#define getSize(x) ((x) >> 4)
#define create (sz,last,used) \
(((sz) & 0x0fffffff) << 4) | \
(((last) & 1) << 1) | \
(((used) & 1))
You'll find your code becomes a lot more readable if you provide the "functions" to do the work and give them sensible names like the above. Otherwise your code is peppered with bit manipulation instructions that are harder to understand.
Just keep in mind the normal rules for macros, things like not passing in things like x++
if your macros use it more than once (which isn't actually the case here). If you want to be ultra-safe, you can do them as functions.
Equivalent functions would be:
unsigned int setLast (unsigned int *x) { *x |= 2; return *x; }
unsigned int clrLast (unsigned int *x) { *x &= ~2; return *x; }
unsigned int isLast (unsigned int x) { return x & 2; }
unsigned int setUsed (unsigned int *x) { *x |= 1; return *x; }
unsigned int clrUsed (unsigned int *x) { *x &= ~1; return *x; }
unsigned int isUsed (unsigned int x) { return x & 1; }
unsigned int getSize (insigned int x) { return x >> 4; }
unsigned int create (unsigned int sz, unsigned int last, unsigned int used) {
unsigned int ret =
((sz & 0x0fffffff) << 4) |
((last & 1) << 1) |
((used & 1));
return ret;
}