1

I've done a test and printed a lot of different kind of pointers and it seems like they are always 8 bytes.

void fx(void) {}

int main() {
    printf("%lu\n", sizeof(int*));
    printf("%lu\n", sizeof(int**));
    printf("%lu\n", sizeof(int****));
    printf("%lu\n", sizeof(float*));
    printf("%lu\n", sizeof(double*));
    printf("%lu\n", sizeof(void*));
    printf("%lu\n", sizeof(char*));
    void(*p)(void);
    printf("%lu\n", sizeof(p));    
}

All these printfs give me 8 Bytes.

My questions:

  1. Do all different kinds of pointers have the same size in 64-bit systems (8 bytes) or 32-bit systems (4 bytes)?
  2. If not, why is that?
chqrlie
  • 131,814
  • 10
  • 121
  • 189
alessio solari
  • 313
  • 1
  • 6
  • 2
    Shouldn't it be `%lu` -> `%zu`? – Jabberwocky Aug 03 '23 at 15:04
  • https://stackoverflow.com/questions/3520059/does-the-size-of-pointers-vary-in-c – Mat Aug 03 '23 at 15:05
  • @EugeneSh. wouldn't that break printfs with the `%p` format specifier? What would `printf("%p", (void*)ptr)` do if the size of `ptr` is variable? – Jabberwocky Aug 03 '23 at 15:07
  • 2
    @Jabberwocky: that's not a problem and it is precisely the reason why you must cast `ptr` as `(void *)ptr`. If the size of some types of pointers differed from others, the size of `void *` would probably need to be the largest as roundtrip conversions is defined for all data types. – chqrlie Aug 03 '23 at 15:10
  • 1
    Jabberwocky, %lu is correct because size_t is an unsigned long – alessio solari Aug 03 '23 at 15:10
  • @Jabberwocky I have removed my comment as there a link to another question answering it. But to yours, the cast to `void*` can be implemented in a way that it will convert to the "right" memory address (absolute? or just equivalent `void*` if it isn't absolute) – Eugene Sh. Aug 03 '23 at 15:11
  • https://stackoverflow.com/questions/2524611/how-can-one-print-a-size-t-variable-portably-using-the-printf-family – Mat Aug 03 '23 at 15:15
  • Jabberwocky, the sizeof operator evaluates to a size_t which is then used by the printf function. The pointer doesn't have anything to to with that. It is perfectly correct to write printf("%lu\n",sizeof(*int)); – alessio solari Aug 03 '23 at 15:18
  • @alessiosolari hmm, on my platform `size_t` is an `unsigned __int64`... – Jabberwocky Aug 03 '23 at 15:21
  • 3
    @alessiosolari *%lu is correct because size_t is an unsigned long* ORLY? Try that on 64-bit Windows. So no, "size_t is an unsigned long" is wrong. `size_t` is [defined as](https://port70.net/~nsz/c/c11/n1570.html#7.19p2) "which is the unsigned integer type of the result of the sizeof operator;" `size_t` **is** `size_t`. It it not `unsigned long` or any other type. – Andrew Henle Aug 03 '23 at 15:21
  • 6
    @alessiosolari The appropriate `printf` specifier for [`size_t`](https://en.cppreference.com/w/c/types/size_t) **is** `%zu`, because `size_t` is an implementation defined type. You cannot rely on it being an `unsigned long` (`%lu`); this is not portable. – Oka Aug 03 '23 at 15:21
  • I worked with a C implementation that had 32-bit pointers in a 64-bit system. The pointer size was chosen to reduce memory use in pointer-heavy data structures. – Eric Postpischil Aug 03 '23 at 15:42
  • @EricPostpischil Was `long` also 32 bits on that implementation? If so, I guess that would be the ILP32 data model like the Linux x32 ABI? – Ian Abbott Aug 03 '23 at 16:28

2 Answers2

3
  1. Have different kind of pointers the same size in 64 bit systems (8 bytes) or 32 bit systems (4 bytes) ?

That is not a rule imposed by C.

It is typically true of modern C implementations for modern architectures that 64-bit systems have 64-bit pointers (for all data types and for functions) and 32-bit systems have 32-bit pointers (for all data types and for functions), but historically, there have been C implementations with function pointers that differed in size from object pointers. There have also been implementations with object pointers of varying sizes. I am not aware of any implementations where object pointers differed in size on the basis of the pointed-to type, but C allows that.

  1. If not why is that ?

Why should C forbid it? The underlying idea is that C implementations should be free to match data types to the underlying hardware as naturally and efficiently as possible. There is more diversity even in modern hardware than you may appreciate, and there is much more if you factor in historic hardware. And who knows what may be wanted for future hardware?

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 2
    IIRC, `int` pointers had a smaller size than other pointer types on the original Cray-1 supercomputers. – chqrlie Aug 03 '23 at 16:51
2

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.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    I think if there will be 128bit micros they will not have 128 bits addresses as it will not have any sense – 0___________ Aug 03 '23 at 20:42
  • 1
    @0___________ Why not? Memory mapping truly huge amounts of files is something you can do with lots of virtual address space, even if physical RAM is much smaller. https://en.wikipedia.org/wiki/RISC-V RV128I has "a 128-bit flat address space". It only exists on paper AFAIK, and the spec for it isn't finalized. (Probably because 64-bit is still big enough for now.) I guess maybe a CPU aimed at bigint calculations might have 128-bit registers but still use 64-bit pointers. But for most ISAs, if they really wanted to accelerate bigint stuff, they'd provide instructions using SIMD regs. – Peter Cordes Aug 03 '23 at 20:47