That is, if you had a byte pointer to the first element and wanted to move to the second element, the stride is the number of bytes distance you’d need to advance the pointer.

Why would size and stride be different? That takes us to our final magic number of memory layout.

Alignment

Imagine the computer fetched eight bits, or one byte of memory at a time. Asking for byte 1 or byte 7 takes the same amount of time each.

Then you upgrade to a 16-bit computer, which access data in 16-bit words. You still have old software that wants to access data by the byte but imagine the possible magic here: if the software asked for byte 0 and byte 1, the computer can now do a single memory access for word 0 and split up the 16-bit result.

Byte-level memory access is twice as fast in this ideal case! 🎉

Now say a rogue program put in a 16-bit value like this:

Then you ask the computer for the 16-bit word at byte location 3. The problem is the value is misaligned. To read it, the computer needs to read the word at location 1, chop it in half, read the word at location 2, chop it in half, then paste the two halves together. That’s two separate 16-bit memory reads to access a single 16-bit value — twice as slow as it should be! 😭

On some systems, unaligned access is worse than slow — it’s not allowed entirely, and will crash the program.

Simple Swift Types

In Swift, the simple types such as Int and Double have the same alignment value as their size. A 32-bit (4 byte) integer has a size of 4 bytes and needs to be aligned to 4 bytes.

The stride is also 4, meaning values in a contiguous buffer are 4 bytes apart. No padding needed.

Compound Types

Now back to our Puppy struct, which has an Int and a Bool property. Consider again the case where values are right up against each other in a buffer:

The Bool values are happy where they are, since they have alignment=1. But the second integer is misaligned. It’s a 64-bit (8-byte) value with alignment=8 and its byte location is not on a multiple of 8. ❌

Remember the stride of this type was 16, meaning the buffer actually looks like this:

We’ve preserved the alignment requirements of all the values inside the struct: the second integer is at byte 16, which is a multiple of 8.

That’s why the struct’s stride can be greater than its size: to add enough padding to fulfill alignment requirements.

Calculating Alignment

So here at the end of our journey, what is the Puppy struct type’s alignment?

MemoryLayout<Puppy>.alignment// returns 8

The alignment of a struct type is the maximum alignment out of all its properties. Between an Int and a Bool, the Int has a larger alignment value of 8, so the struct uses it.

The stride then becomes the size rounded up to the next multiple of the alignment. In our case:

What are the size, stride, and alignment for these two structs? 🤔 (spoiler)

The Closing Brace

In the end, say you have an UnsafeRawPointer (aka a void * in C). You know the type of thing it points to. Where do size, stride, and alignment come into the picture?

Size is the number of bytes to read from the pointer to reach all the data.

Stride is the number of bytes to advance to reach the next item in the buffer.

Alignment is the the “evenly divisible by” number that each instance needs to be at. If you’re allocating memory to copy data into, you need to specify the correct alignment (e.g. allocate(byteCount: 100, alignment: 4)).

For most of us, most of the time, we probably deal with high-level collections such as arrays and sets and don’t need to consider the underlying memory layout.

In other cases, you work with lower-level APIs on the platform or interop with C code. If you have an array of Swift structs and need your C code to read it (or vice versa) you’ll need to worry about allocating a buffer with the correct alignment, making sure padding inside the struct lines up, and making sure you have the right stride value so you can interpret the data correctly.

And as we saw, even calculating size isn’t as simple as it seems — there’s some interplay between the size and alignment of each property that determines a struct’s overall size. So understanding all three means you’re on your way to being a master of memory.