When is uintptr_t preferred over intptr_t?

It is mostly a stylistic argument (an optimizing compiler would probably generate the same, or very similar, code). However, pointer compares may be a tricky issue.

Remember than in purely standard C pointer compare is roughly meaningful only for pointers to the same aggregate data. You are probably not allowed to compare two results from malloc, e.g. to keep a sorted array of pointers.

I would keep them as void*, or else as uintptr_t. The signed intptr_t has the inconvenience to seggregate negative and positive numbers, and where they are coming from significant application pointers, this is probably not welcome.

Notice that a void* cannot be dereferenced: as an uintptr_t, you have to cast it to do something useful with the data pointed by the address; however void* pointers can be passed to routines like memset

PS. I am assuming an ordinary processor (e.g. some x86, PowerPC, ARM, ...) with a flat virtual address space. You could find exotic processors -some DSPs perhaps- with very significant differences (and perhaps on which intptr_t is not always meaningful; remember that on the 1990s Cray Y-MP supercomputers sizeof(long*) != sizeof(char*); at that time C99 did not exist, and I am not sure its <stdint.h> could be meaningful on such machines)


You should pick the type appropriate for the given system and program. Most of the time, pointers are positive address values, in which case uintptr_t is the correct type. But some systems use negative addresses as a way to express kernel space, as explained here: Can a pointer (address) ever be negative? This would be the reason why there are two different types.


As for (u)intptr_t vs void* for a generic pointer type, the former is preferred in rugged, professional programs. There are many problems/bug sources associated with pointer types:

  • All manner of different pointer types are most often not compatible with each other and cannot alias. This is a problem with object pointers as well as function pointers.
  • You often have type qualifiers like const, which makes pointer conversions to/from that type questionable or poorly-defined.
  • Conversions to/from void* and other pointer types happen implicitly, making it easy for bugs related to using the wrong pointer type to slip through unnoticed. This was fixed in C++, but remains a hazard in C. Take for example the old but classic "I forgot to include stdlib.h while using malloc in C90" bug.
  • Performing arithmetic on a pointer comes with numerous pitfalls, because you can only safely do arithmetic on a pointer which points at an allocated array. However, one can often have a memory address for a lot of other reasons than pointing at an array, as anyone working with embedded systems knows.
  • You can't even perform pointer arithmetic calculations on a void*. Doing so relies on non-standard compiler extensions.

That being said, a whole lot of legacy code relies on void pointers, and it's perfectly fine to use them in a restricted context. Some examples would be canonical code relying on generic callback functions: bsearch, qsort, pthreads and similar.

I would however not recommend to use void pointers when designing new C programs - they are, in my opinion, best regarded as a dangerous feature of the past. There exist better and safer methods of generic C programming nowadays, such as C11 _Generic, tricks using designated initializers, passing parameters as array pointers (to VLA), bounds-checking at compile time with static_assert etc. Some examples can be found in my answer here: How to create type safe enums?.


That sounds very strange, since it's going to require casts. A void * in C has the huge advantage that it converts to/from other object pointer types without casts, which is way clean.

That said uintptr_t might make sense if you want to do things to the bits of the pointer that you can't do as sensibly with a signed integer (such as shifting them to the right, for instance).


If you want to manipulate the value arithmetically (e.g., to encrypt it), you have much more flexibility with an unsigned type (where arithmetic wraps around) than with a signed type (where arithmetic overflow gives undefined behavior).