liveBook · Manning

In-memory format

As explained in Modern C

An object representation is the concrete representation of a value in an object's storage, such as memory or persistent storage.

One value, four observed bytes

On a platform where sizeof(unsigned) is 4, an unsigned value of 0xAABBCCDD was inspected through a union containing both an unsigned member and an unsigned char array. The printed bytes were DD, CC, BB, AA, in that order. The value did not change; the array exposed the order of its stored bytes on that platform. section 12.2

An object's in-memory format, or object representation, is its representation in storage. C models each object as a collection of bytes that can be viewed as an array of unsigned char. This differs from a type's binary representation: that model describes possible values and determines operations, but does not completely specify storage in memory or on disk. section 12.0 section 5.1.3

Figure 12.1. The different levels of the value-memory model for an int32_t. Example of a platform that maps this type to a 32-bit signed int that has two’s complement sign representation and little-endian object representation.

The diagram separates an int32_t value's binary representation from its little-endian object representation; both are shown for a specified example platform. section 12.0

2   #include <inttypes.h>
3
4   typedef union unsignedInspect unsignedInspect;
5   union unsignedInspect {
6     unsigned val;
7     unsigned char bytes[sizeof(unsigned)];
8   };
9   unsignedInspect twofold = { .val = 0xAABBCCDD, };


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":0},{\"line\":0,\"ch\":1}],[{\"line\":0,\"ch\":4},{\"line\":0,\"ch\":12}],[{\"line\":1,\"ch\":0},{\"line\":1,\"ch\":1}],[{\"line\":2,\"ch\":0},{\"line\":2,\"ch\":1}],[{\"line\":2,\"ch\":4},{\"line\":2,\"ch\":17}],[{\"line\":3,\"ch\":0},{\"line\":3,\"ch\":1}],[{\"line\":2,\"ch\":12},{\"line\":2,\"ch\":17}],[{\"line\":3,\"ch\":4},{\"line\":3,\"ch\":9}],[{\"line\":4,\"ch\":0},{\"line\":4,\"ch\":1}],[{\"line\":2,\"ch\":18},{\"line\":2,\"ch\":26}],[{\"line\":3,\"ch\":10},{\"line\":3,\"ch\":18}],[{\"line\":4,\"ch\":6},{\"line\":4,\"ch\":14}],[{\"line\":5,\"ch\":6},{\"line\":5,\"ch\":14}],[{\"line\":7,\"ch\":4},{\"line\":7,\"ch\":12}],[{\"line\":5,\"ch\":0},{\"line\":5,\"ch\":1}],[{\"line\":5,\"ch\":6},{\"line\":5,\"ch\":19}],[{\"line\":5,\"ch\":26},{\"line\":5,\"ch\":32}],[{\"line\":2,\"ch\":18},{\"line\":2,\"ch\":26}],[{\"line\":3,\"ch\":10},{\"line\":3,\"ch\":18}],[{\"line\":4,\"ch\":6},{\"line\":4,\"ch\":14}],[{\"line\":5,\"ch\":6},{\"line\":5,\"ch\":14}],[{\"line\":7,\"ch\":4},{\"line\":7,\"ch\":12}],[{\"line\":6,\"ch\":0},{\"line\":6,\"ch\":1}],[{\"line\":7,\"ch\":0},{\"line\":7,\"ch\":1}]]"}
!@%STYLE%@!

The union overlays an unsigned value and an unsigned char array of exactly sizeof(unsigned) bytes, providing two views of the same object. section 12.2

0      ~/build/modernC% code/endianness
1      value is 0xAABBCCDD
2      byte[0]: 0xDD
3      byte[1]: 0xCC
4      byte[2]: 0xBB
5      byte[3]: 0xAA


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":0},{\"line\":0,\"ch\":1}],[{\"line\":1,\"ch\":16},{\"line\":1,\"ch\":17}],[{\"line\":2,\"ch\":12},{\"line\":2,\"ch\":13}],[{\"line\":3,\"ch\":16},{\"line\":3,\"ch\":17}],[{\"line\":4,\"ch\":16},{\"line\":4,\"ch\":17}],[{\"line\":5,\"ch\":16},{\"line\":5,\"ch\":17}],[{\"line\":1,\"ch\":0},{\"line\":1,\"ch\":1}],[{\"line\":3,\"ch\":12},{\"line\":3,\"ch\":13}],[{\"line\":2,\"ch\":0},{\"line\":2,\"ch\":1}],[{\"line\":4,\"ch\":12},{\"line\":4,\"ch\":13}],[{\"line\":3,\"ch\":0},{\"line\":3,\"ch\":1}],[{\"line\":5,\"ch\":12},{\"line\":5,\"ch\":13}],[{\"line\":4,\"ch\":0},{\"line\":4,\"ch\":1}],[{\"line\":5,\"ch\":0},{\"line\":5,\"ch\":1}]]"}
!@%STYLE%@!

The printed byte indices show low-order digits first on the tested machine, not a storage order guaranteed for every implementation. section 12.2

TAKEAWAY: Do not infer a universal byte order from one run. The in-memory order of an arithmetic type's representation digits is implementation-defined: low-order digits first is little-endian; high-order digits first is big-endian. A cast to unsigned char* offers another way to inspect an object's bytes, but pointer conversions require care because a different pointer type can change how an object is interpreted. section 12.2 section 12.5

How Modern C covers it

How deep it goes
Defines the byte-based object model and works through byte inspection and endianness, without treating observed storage order as universal.
Examples
Uses an unsigned value of 0xAABBCCDD whose inspected bytes print DD, CC, BB, AA on one machine.
What it assumes
Builds on C values and types, then uses sizeof, unions, and pointers to make storage observable.
What you can do after
Evaluate/understand it: Interpret inspected bytes while recognizing that their order can differ across implementations.
How it connects
Connects object representation to binary representation, unions, unsigned char pointers, explicit conversions, and endianness.

Also explained in

Compare how 2 books explain in-memory format →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Modern C. The section links point to the parts of the book it draws on.