Struct Padding in a Binary Log Format
A data logger stores readings in:
struct reading {
char sensor; /* 'A' or 'B' */
int sample;
short status;
};
The host compiler uses 4-byte int alignment, and sizeof(struct reading) == 12. The logger writes the struct with fwrite(&r, sizeof r, 1, fp) and later reads it back with fread. A new ARM build is compiled with -fpack-struct, where sizeof is 7. Records read by the new build show sample values in the billions and status garbage. Separately, a unit test zeroes two structs with memset, assigns identical sensor/sample/status values, and asserts memcmp(&a, &b, sizeof a) == 0; it fails intermittently. The team wants a file format that both builds can read and a reliable equality check.
1. Compute the offsets and `sizeof` for the host layout and for the packed build. Explain why the packed build reads huge `sample` values.
Read the worked answer
Host, with 4-byte int alignment: sensor at offset 0; three padding bytes at 1–3 so sample starts at a multiple of 4; sample at 4–7; status at 8–9; two tail padding bytes at 10–11; sizeof is 12, a multiple of the strictest alignment (4). Packed: sensor at 0, sample at 1–4, status at 5–6, sizeof 7. The host writes 12 bytes per record with sample starting at byte 4. The packed reader takes bytes 1–4 as sample: three padding bytes from the host plus only the low-order byte of the host sample on a little-endian machine. For example, if the host sample is 1234 (0x000004D2), the packed reader may see 0xD2000000 because the low byte is placed in the most-significant position. status then takes bytes 5–6, which are higher bytes of the host sample. Every record is also misaligned by 5 bytes relative to the next, so errors accumulate. The file format is tied to one ABI, not to the data.
2. Two structs are zeroed with `memset`, assigned identical field values, and compared with `memcmp`; the test fails intermittently. Explain why and give a reliable equality check.
Read the worked answer
memcmp compares every byte, including padding bytes that are not part of any field. Padding bytes have indeterminate values after ordinary field assignments; even if both objects are memset to zero first, a later assignment may write only the field bytes and leave padding unchanged, or the compiler may copy the struct in a way that does not preserve padding. The C standard does not guarantee that padding is copied or that it has any particular value. Comparing padding is therefore not a reliable equality test for the logical record. Compare fields explicitly: a.sensor == b.sensor && a.sample == b.sample && a.status == b.status. If a byte-wise comparison is required for a hash or file image, serialize to a defined byte representation first, not to the in-memory struct. Also avoid memcmp on structs containing padding, pointers, or floating-point values where representation details matter.
3. Design a record format that both builds can read. Specify field widths, byte order, and how the writer avoids padding.
Read the worked answer
Define the on-disk record independently of the C struct: one byte for sensor, four bytes for sample as a two's-complement 32-bit integer, two bytes for status as an unsigned 16-bit integer, for a fixed 7-byte record, or add a version/length prefix. Choose one byte order, for example little-endian, and encode explicitly:
unsigned char rec[7];
uint32_t s = (uint32_t)r.sample;
uint16_t st = (uint16_t)r.status;
rec[0] = (unsigned char)r.sensor;
rec[1] = s & 0xFF; rec[2] = (s >> 8) & 0xFF;
rec[3] = (s >> 16) & 0xFF; rec[4] = (s >> 24) & 0xFF;
rec[5] = st & 0xFF; rec[6] = (st >> 8) & 0xFF;
fwrite(rec, sizeof rec, 1, fp);
Read the bytes back into uint32_t/uint16_t temporaries and convert to the host types; do not cast a byte buffer to struct reading *, because that reintroduces alignment and padding. Use uint8_t, int32_t, uint16_t from stdint.h for the in-memory staging types, and document that int on the host is 4 bytes. If the format must be portable to systems where int is not 32 bits, the fixed-width staging types are mandatory. A magic number or version byte lets the reader reject incompatible files.