I'll try.
Chunk of memory that's treated as “smallest indivisible piece” by CPU. Why it's called “cache line” is separate issue, but the idea is that memory is split, in practice, into “cache lines”, not into bytes. Sending separate bytes is just too slow!
These cache lines are 64 bytes in size today (although Apple went to 128bytes, apparently).
And “bad things” start happening when one memory access tries to access data from two different cache lines.
The trouble happens below instructions. Memory that CPU is accessing is split into “cache lines”. Instruction may, logically, read 1, 2, 4, or maybe even 10 bytes (yes, such instructions do exist), but it's all an illusion that CPU have to provide to not make compiler designers and programmers crazy: actual access to memory always[1] happens in terms of cache line… and we want to ensure that instructions wouldn't need to read two cache lines, but would always read one… yet the trouble is: size of cache line changes with time!
But if cache line is power of two (was always the case till now) and we align out small elements (like u16 or f128) at the power of two address, too – then we would never need to “cross the cache line” to read them.
That's the whole story behind alignment and padding, in a nutshell.
When “atomic” memory access tries to read data from two “smallest” pieces of memory.
I was talking about ARMv1 there. That's year 1985. Cache line, back there, was 4 bytes[2]. And if you asked CPU to “cross” the cache line it would whine and do something like this:
Address:
0 1 2 3 | 4 5 6 7 | 8 9 10 11 12 | 13 15 15 16 |
Bytes:
a b c d | e f g h | i j k l m | n o p q | // 4 bytes from address 6: g h e f
And when you asked for “4 bytes from address 6” CPU was giving you bytes “[g h e f]” (in that order). Because “crossing the cache line border[3]” was too hard for it. One read = one memory access, easy and simple. For hardware.
Today most CPUs are ready to “cross the cache line border”, but only with “normal” memory access. And that's slower than not to “cross the cache line border”. Because to read [g h i j] CPU would need to read two cache lines and then “combine two pieces into one”.
ARMv1 trick made it possible to access [g h] relative cheaply. Read four bytes [g h e f], change g h to G H, store the back and voila:
Address:
0 1 2 3 | 4 5 6 7 | 8 9 10 11 12 | 13 15 15 16 |
Bytes:
a b c d | e f G H | i j k l m | n o p q |
Exactly what we needed!
Hardware guys were quite proud of that trick, but software guys were not amused: the idea that if you read 4 bytes starting from address 4 then you get [g h e f] and not [g h i j] made them angry, for some reason.
ARMv1 could only access 1 byte or 4 bytes. It simply had no instructions to access two bytes. But compiler could emulate that access: just read 4 bytes, use these 2 bytes that you need, ignore the rest. The fact that attempt to read 4 bytes from address 137 gives you not bytes 137, 138, 139, 140 (like modern CPU would do) but bytes 137, 138, 139, 136 is not important if you only care about bytes 137 and 138 (two other bytes you would still read, preserve, the store back, if needed).
Today all that done automatically, in hardware, without compiler or software developer involved… but it's still done and we want to avoid that, if possible.