"...in a scalable way?" -- no can do, C does not have memory layout semantics.
Note:
I have never found a compiler to give an incorrect value for "sizeof(T)",
The compiler is telling you that an array of N struct foo objects,
as you have presented here, will take up 8*N bytes.
I have, however, on one project, coded to force the 'proper',
expected, and shall we say most compact 'packing' of an object,
without using a pragma. (I am of the opinion that pragmas are
non-portable. However, every compiler that I have investigated
which contained pragmas, seemed to provide excellent results. The
pragma was occasionally not spelled the same)
In other words, if you really need a 5 byte foo object it is possible, and I think relatively easy, to accomplish. But the following scheme quickly gets tiresome if your
object foo grows to have a lot of fields.
In my embedded software project, the remote processor used a different processor, and different compiler, on different build machine, and they used their compiler's pragma pack.
Our software had to describe an object that complied with their packing, because thousands of that subsystem had already shipped. Our code had to be able to interact. These systems communicated with binary packets sent across a proprietary back plane. (Neither C nor C++ provide memory layout symantics. Ada did, but does anyone care anymore?)
We tried a number of solutions, each with pro's and con's.
The following is styled after 1 of maybe 5 successful mechanisms ... and this one is easy to remember.
Two steps:
- Declare your object using array of byte.
- Add the appropriate setters and getters, 1 of each per field.
Note: you will learn endian-ness, look into the macro's ntoh and hton
namespace fooNameSpace
{
struct foo
{
private:
uint8_t pfd[5]; //packed_foo_data
public:
void barSet(uint32_t value)
{
pfd[0] = static_cast<uint8_t>((value & 0xff000000) >> 24); // msbyte
pfd[1] = static_cast<uint8_t>((value & 0x00ff0000) >> 16);
pfd[2] = static_cast<uint8_t>((value & 0x0000ff00) >> 8);
pfd[3] = static_cast<uint8_t>((value & 0x000000ff) >> 0); // lsbyte
}; // you will need to fix for endian of target
uint32_t barGet(void)
{
uint32_t value = 0;
value |= pfd[0] << 24; // msbyte
value |= pfd[1] << 16;
value |= pfd[2] << 8;
value |= pfd[3] << 0; // lsbyte
return (value);
}; // fix for endian of target
void bazSet(uint8_t value) { pfd[4] = value; }
uint8_t bazGet(void) { return pfd[4]; };
};
}
Some of my team tried to create a union ...
and found out it got confusing when the union
must match the remote compiler, target, endian-ness
on the local host and compiler. Kinda confused the
emulator, too.