I have need to pack four signed bytes into 32-bit integral type. this is what I came up to:
int32_t byte(int8_t c) { return (unsigned char)c; }
int pack(char c0, char c1, ...) {
return byte(c0) | byte(c1) << 8 | ...;
}
is this a good solution? Is it portable (not in communication sense)? is there a ready-made solution, perhaps boost?
issue I am mostly concerned about is bit order when converting of negative bits from char to int. I do not know what the correct behavior should be.
Thanks
"Goodness"
IMHO, this is the best solution you're going to get for this. EDIT: though I'd use
static_cast<unsigned int>
instead of the C-style cast, and I'd probably not use a separate method to hide the cast....Portability:
There is going to be no portable way to do this because nothing says
char
has to be eight bits, and nothing saysunsigned int
needs to be 4 bytes wide.Furthermore, you're relying on endianness and therefore data pack'd on one architecture will not be usable on one with the opposite endianness.
is there a ready-made solution, perhaps boost?
Not of which I am aware.
char
isn't guaranteed to be signed or unsigned (on PowerPC Linux, char defaults to unsigned). Spread the word!What you want is something like this macro:
It's ugly mainly because it doesn't play well with C's order of operations. Also, the backslash-returns are there so this macro doesn't have to be one big long line.
Also, the reason we cast to uint8_t before casting to uint32_t is to prevent unwanted sign extension.
This is based on Grant Peters and Joey Adams' answers, extended to show how to unpack the signed values (the unpack functions rely on the modulo rules of unsigned values in C):
(As Steve Jessop noted in comments, there is no need for separate
pack_s
andpack_u
functions).(These are necessary rather than simply casting back to
int8_t
, because the latter may cause an implementation-defined signal to be raised if the value is over 127, so it's not strictly portable).You can avoid casts with implicit conversions:
The idea is that you see "convert all the parameters correctly. Shift and combine them", rather than "for each parameter, convert it correctly, shift and combine it". Not much in it, though.
Then:
Output:
This is as portable as the
uint32_t
,uint8_t
andint8_t
types. None of them is required in C99, and the header stdint.h isn't defined in C++ or C89. If the types exist and meet the C99 requirements, though, the code will work. Of course in C the unpack functions would need a function parameter instead of a template parameter. You might prefer that in C++ too if you want to write short loops for unpacking.To address the fact that the types are optional, you could use
uint_least32_t
, which is required in C99. Similarlyuint_least8_t
andint_least8_t
. You would have to change the code of pack_helper and unpack_u:To be honest this is unlikely to be worth it - chances are the rest of your application is written on the assumption that
int8_t
etc do exist. It's a rare implementation that doesn't have an 8 bit and a 32 bit 2's complement type.You could also let the compiler do the work for you.
Etc.
I liked Joey Adam's answer except for the fact that it is written with macros (which cause a real pain in many situations) and the compiler will not give you a warning if 'char' isn't 1 byte wide. This is my solution (based off Joey's).
I've omitted casting c0->c3 to a uint32_t as the compiler should handle this for you when shifting and I used c-style casts as they will work for either c or c++ (the OP tagged as both).