What is alignment?

This is exactly where the difference comes from. The alignment of a pointer to a type has nothing to do with the type itself. The pointer is just an address to an arbitrary region in memory and is always (usually?) the same size, regardless of what is points to. So its alignment is by default always the natural alignment for its own size in memory, i.e. 8 bytes on 64-bit systems. When you’re calling @alignOf(*T) what you’re getting is exactly that, the alignment of the pointer itself, not of the memory it points to.

However @alignCast doesn’t cast the alignment of the pointer itself, but the alignment of the address represented by the pointer. This is also what the align(n) pointer attribute specifies. @alignCast asserts that the address represented by its operand is a multiple of the align attribute of its result pointer type.

So because anyopaque is only guaranteed to be aligned to a multiple of 1 *anyopaque has an implicit align(1) attribute (even though it is aligned to 8 bytes itself) and an @alignCast is necessary if you want to cast to a pointer whose align attribute is >1.

2 Likes

What’s actually being specified is the number of mandatory trailing zeroes in the address: for align(8), it’s three, and so on.

The reason those are the only valid alignments is because any ISA from the last 40 years (perhaps more?) only deals in powers of two.

The reason why they only deal in powers of two? Is because the circuitry to handle that can count trailing zeroes. That makes it practical to build MMUs, caches, and such nice things we have come to expect computers to have.