If not why is that?
Because memory is byte-addressable on modern mainstream CPUs, which are all 32 or 64-bit with a flat memory model, where pointers are just linear integers.
They have enough address bits that it's not a problem that the low 2 bits of every int*
is always 0
(assuming a C implementation with alignof(int) == 4
.) So char*
doesn't need a separate chunk of pointer data to address the byte-within-word after addressing a wider chunk of memory.
Also, function pointers (which you didn't print the size of) are also just 32 or 64-bit integers on modern mainstream systems.
Are C pointers all 8 Bytes wide in a 64 bit architecture?
No, and that's a separate question. There are ILP32 ABIs for 64-bit CPUs you can compile for, to save space on pointers. (Int/Long/Pointer = 32-bit). Examples include AArch64's ILP321, and x86-64's x32.
e.g. gcc -mx32
, which is very different from compiling for 32-bit mode (gcc -m32
). The CPU is still in 64-bit mode for x32, so you have 16 registers that are 64-bit wide, so long long
is still efficient for computing. And it can't run on 32-bit-only CPUs.
But it's still an ILP32 ABI, intentionally limiting itself to only addressing 4 GiB of memory in one process, in exchange for sizeof(anything *) == 4
, which makes pointer-heavy data structures often half the size, improving data-cache density. e.g. struct node { node *left, right; int key; }
is 12 bytes in x32 (like in 32-bit code), vs. 24 bytes in 64-bit code: 2 pointers = 16 bytes + int (4 bytes), plus 4 bytes of padding to make the struct size a multiple of its required alignment, which is 8 inherited from the pointer members.
Footnote 1: Note that terminology for describing ABIs like ILP32 and LP64 (most Unix ABIs for 64-bit CPUs) lump all pointer sizes together because that is in fact widespread. (https://unix.org/version2/whatsnew/lp64_wp.html). It's not required by the ISO C standard, but rather is due to how computer architecture has evolved.
Once transistor budgets made it sensible to build 32-bit CPUs, we did that and could abandon all the complicated ways of addressing more memory than a pointer the width of one register could manage. (Like 16-bit x86's segmentation, which some compilers exposed in C as small vs. large memory models and near vs. far pointers.)
When 32-bit became a limitation, we developed 64-bit ISAs primarily to address more memory. (Also processing 64 bits at a time is nice for BigInt, and 64-bit timestamps and other use-cases for 64-bit integers, and on CPUs without SIMD, string algorithms going 8 bytes at a time instead of 4. But most of these things are just a bonus, with addressing more memory being the primary motivation.)