GCC vs Clang when packing array into uint64_t

2026-06-16

Stack Overflow: View Question

Tags: c++, gcc, hash, x86, memcpy

Score: 15 | Views: 563

The asker wants the fastest possible hash for a std::array<uint8_t,6> key. Their idea: pack the six bytes into a uint64_t and then hand it to std::hash<uint64_t>. Two natural ways to do the packing:

The interesting part is that GCC and Clang make markedly different codegen choices for these two idioms. Clang typically recognizes the shift-and-OR pattern as a "load a 6-byte little-endian value" and collapses it into a single unaligned load plus a mask. GCC often leaves the shifts as actual shifts, producing a long sequence of movzx/shl/or instructions. For the memcpy version, both compilers do well — typically a 4-byte load combined with a 2-byte load — but the exact instruction selection (movbe? two movs and a shift? a single mov + mask?) varies.

Why it's hard: 6 isn't a power of two, so there's no clean single-instruction load. The compiler must reason that reading two extra bytes past the array is illegal in general, but safe here if you mask the result. That's an analysis pass each compiler implements differently. There's also the question of whether the hash quality matters — masking off the upper 16 bits matters for correctness of equality, but if you feed the raw 64-bit register (with whatever garbage was in the high bytes) into std::hash, you get wrong answers across calls if the garbage differs.

Direction toward an answer:

Gotchas: reading 8 bytes when only 6 are guaranteed valid is UB if the array sits at the end of a page — ASan and valgrind will (correctly) complain. Endianness flips the byte order between the two idioms, which matters if the hash is persisted or compared across machines. And on hot paths, the surrounding code (where the array lives, alignment, whether it's already in a register) often dwarfs the packing cost.

The challenge: A 6-byte pack is a deceptively simple operation that exposes real differences in how GCC and Clang's load-combining passes recognize hand-written byte assembly versus a plain memcpy.

All newsletters