57

I know it is an integer type that can be cast to/from pointer without loss of data, but why would I ever want to do this? What advantage does having an integer type have over void* for holding the pointer and THE_REAL_TYPE* for pointer arithmetic?

EDIT
The question marked as "already been asked" doesn't answer this. The question there is if using intptr_t as a general replacement for void* is a good idea, and the answers there seem to be "don't use intptr_t", so my question is still valid: What would be a good use case for intptr_t?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Baruch
  • 20,590
  • 28
  • 126
  • 201
  • 1
    See also: http://stackoverflow.com/questions/6326338/why-when-to-use-intptr-t-for-type-casting-in-c – i_am_jorf Jan 28 '16 at 20:20
  • 1
    Try writing memory management code of an OS without a safe type. – too honest for this site Jan 28 '16 at 20:39
  • Nominate for re-open as listed dupe answer "Is it a good idea to use intptr_t as a general-purpose storage", which is close to this post, but not quite. – chux - Reinstate Monica Jan 28 '16 at 21:03
  • 1
    In my experience its main use is for tidying up legacy code that assumed casting an int to `void *` and back again later was a valid technique – M.M Jan 28 '16 at 21:35
  • Note: The post essential also applies to `uintptr_t`. – chux - Reinstate Monica Jan 28 '16 at 21:48
  • 1
    I found it useful here: http://stackoverflow.com/questions/6326534/can-storing-unrelated-data-in-the-least-significant-bit-of-a-pointer-work-reliab – Jeremy Friesner Jan 28 '16 at 22:01
  • Why do you have `c` and `c++` as tags? Please do not ask about multiple different programming languages in the same question except it is a question about the difference in them. – 12431234123412341234123 Sep 04 '20 at 10:27
  • 1
    @12431234123412341234123 Because this feature is the same in both – Baruch Sep 04 '20 at 13:01
  • 2
    @Baruch C and C++ are 2 different languages and are incompatible. Do not use both tags when you do not ask about differences, similarities, `extern "C"` or something like that. The us of `intptr_t` is may not the same in both, if you would had known that all relevant parts are the same in both language, which is not the case, then you would not have to ask this question in the first place. – 12431234123412341234123 Sep 07 '20 at 13:23
  • 1
    @12431234123412341234123 Yes, they are incompatible. So what? The question applies exactly the same to both – Baruch Sep 07 '20 at 13:37
  • @Baruch; because often, the answer is _not_ the same in both, even when the asker thinks it will be – Mooing Duck Apr 06 '21 at 02:05
  • FWIW, "intptr_t" isn't even mentioned in Jens Gustedt's "Modern C". – David Tonhofer Apr 20 '21 at 20:43
  • 1
    @DavidTonhofer the fact that it doesn't appear in a book doesn't mean it has no usage – phuclv Mar 29 '22 at 01:13

5 Answers5

47

The primary reason, you cannot do bitwise operation on a void *, but you can do the same on a intptr_t.

On many occassion, where you need to perform bitwise operation on an address, you can use intptr_t.

However, for bitwise operations, best approach is to use the unsigned counterpart, uintptr_t.

As mentioned in the other answer by @chux, pointer comparison is another important aspect.

Also, FWIW, as per C11 standard, §7.20.1.4,

These types are optional.

Community
  • 1
  • 1
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • 4
    @5gon12eder: I fully agree, but I know at least one platform which used actually signed memory space. Acknowledged, Transputers are not very up-to-date now, but C also targets more exotic platforms. FYI: That was related to the instruction set and how literals & instructions were composed. Quite interesting subject which shows there once were quite interesting architectures beyond the current quite boring (from the instruction set) ones. – too honest for this site Jan 28 '16 at 20:43
  • Not that I have encountered every possible scenario, but why would you want to do this? And can't it be done with the *real* pointer type? – Baruch Jan 28 '16 at 20:55
  • 2
    Pointer types does not guarantee +1 actually increase the value by 1. `void *` does not have `operator +` although gcc supports it as an extension. Even with `sizeof(char)` (by standard) and `sizeof(void)` (by gcc extension) guaranteed to equal to 1, adding 1 to `char *` or `void *` still does not guarantee increasing their numerical value by 1: examples are non-byte addressable machines( lower bits of pointers are always 0s). So `intptr_t` also makes `+/-` safer. – user3528438 Jan 28 '16 at 21:23
  • 3
    @user3528438 Why would you want to increase the pointer by any number that it not a multiple of the size of the thing it points to? – Baruch Jan 28 '16 at 21:29
  • 5
    @baruch an example: you might want to generate a pointer that's correctly aligned for `int`, so you could take some other pointer and increase it to the next multiple of `sizeof(int)`. (or `alignof(int)`) – M.M Jan 28 '16 at 21:38
  • 2
    @baruch: One example of a bitwise operator unavailable on normal pointers is using exclusive-or to implement xor-linked lists. – doynax Oct 10 '17 at 10:42
25

There's also a semantic consideration.

A void* is supposed to point to something. Despite modern practicality, a pointer is not a memory address. Okay, it usually/probably/always(!) holds one, but it's not a number. It's a pointer. It refers to a thing.

A intptr_t does not. It's an integer value, that is safe to convert to/from a pointer so you can use it for antique APIs, packing it into a pthread function argument, things like that.

That's why you can do more numbery and bitty things on an intptr_t than you can on a void*, and why you should be self-documenting by using the proper type for the job.

Ultimately, almost everything could be an integer (remember, your computer works on numbers!). Pointers could have been integers. But they're not. They're pointers, because they are meant for different use. And, theoretically, they could be something other than numbers.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
18

The uintptr_t type is very useful when writing memory management code. That kind of code wants to talk to its clients in terms of generic pointers (void *), but internally do all kinds of arithmetic on addresses.

You can do some of the same things by operating in terms of char *, but not everything, and the result looks like pre-Ansi C.

Not all memory management code uses uintptr_t - as an example, the BSD kernel code defines a vm_offset_t with similar properties. But if you are writing e.g. a debug malloc package, why invent your own type?

It's also helpful when you have %p available in your printf, and are writing code that needs to print pointer sized integral variables in hex on a variety of architectures.

I find intptr_t rather less useful, except possibly as a way station when casting, to avoid the dread warning about changing signedness and integer size in the same cast. (Writing portable code that passes -Wall -Werror on all relevant architectures can be a bit of a struggle.)

phuclv
  • 37,963
  • 15
  • 156
  • 475
Arlie Stephens
  • 1,146
  • 6
  • 20
8

What is the use of intptr_t?

Example use: order comparing.
Comparing pointers for equality is not a problem.
Other compare operations like >, <= may be UB. C11dr §6.5.8/5 Relational operators.
So convert to intptr_t first.

[Edit] New example: Sort an array of pointers by pointer value.

int ptr_cmp(const void *a, const void *b) {
  intptr_t ia = (intptr) (*((void **) a));
  intptr_t ib = (intptr) (*((void **) b));
  return (ia > ib) - (ia < ib);
}

void *a[N];
...
qsort(a, sizeof a/sizeof a[0], sizeof a[0], ptr_cmp);

[Former example] Example use: Test if a pointer is of an array of pointers.

#define N  10
char special[N][1];

// UB as testing order of pointer, not of the same array, is UB.
int test_special1(char *candidate) {
  return (candidate >= special[0]) && (candidate <= special[N-1]);
}

// OK - integer compare
int test_special2(char *candidate) {
  intptr_t ca = (intptr_t) candidate;
  intptr_t mn = (intptr_t) special[0];
  intptr_t mx = (intptr_t) special[N-1];
  return (ca >= mn) && (ca <= mx);
}

As commented by @M.M, the above code may not work as intended. But at least it is not UB. - just non-portably functionality. I was hoping to use this to solve this problem.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    @M.M I took a shot. What might not work about it? Sequence of `special[0]..special[N-1]` might not match the `intptr_t` versions? or do you see another pitfall? – chux - Reinstate Monica Jan 28 '16 at 21:18
  • @M.M: Why would that not be guaranteed? I don't see any problem. – abelenky Jan 28 '16 at 21:19
  • 1
    Yes, the `intptr_t` values need not have the same ordering as the pointers in the range being tested. Although it is quite likely that they would. – M.M Jan 28 '16 at 21:21
  • 1
    If comparing to pointers from different memory allocations is UB and not guaranteed to work, I don't see how casting them to an integer first would make it work any better. – Baruch Jan 28 '16 at 21:33
  • @baruch well, this way at worst you get the wrong result, but you do not launch nuclear missiles – M.M Jan 28 '16 at 21:36
  • 1
    @baruch Comparing pointers for equality/inequality is OK. Comparing pointers for order within the same array of pointers is OK. Else comparing pointers for order is expressly UB per the C spec. When converted to integers, then is no UB in the order comparison. As [@M.M](http://stackoverflow.com/questions/35071200/what-is-the-use-of-intptr-t/35072150?noredirect=1#comment57868014_35072150) comments, the silos are still quiet. – chux - Reinstate Monica Jan 28 '16 at 21:36
  • @chux While it is defined in the sense that it will not start a nuclear war but rather must return a valid boolean value, what that value is is no ore defined than with the pointer comparison, and so is no more useful. – Baruch Jan 31 '16 at 07:19
  • 3
    @M.M Unless you're writing the nuclear missile launch control system – Lightness Races in Orbit Feb 23 '19 at 13:22
  • 1
    @Baruch it is more useful because undefined behavior could not return any boolean value at all and crash the program instead http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html – MarcH Jul 15 '23 at 13:25
6

(u)intptr_t is used when you want to do arithmetic on pointers, specifically bitwise operations. But as others said, you'll almost always want to use uintptr_t because bitwise operations are better done in unsigned. However if you need to do an arithmetic right shift then you must use intptr_t1. It's usually used for storing data in the pointer, usually called tagged pointer

  • In x86-64 you can use the high 16/7 bits of the address for data, but you must do sign extension manually to make the pointer canonical because it doesn't have a flag for ignoring the high bits like in ARM2. So for example if you have char* tagged_address then you'll need to do this before dereferencing it

    char* pointer = (char*)((intptr_t)tagged_address << 16 >> 16);
    
  • The 32-bit Chrome V8 engine uses smi (small integer) optimization where the low bits denote the type

                |----- 32 bits -----|
    Pointer:    |_____address_____w1| # Address to object, w = weak pointer
    Smi:        |___int31_value____0| # Small integer
    

    So when the pointer's least significant bit is 0 then it'll be right shifted to retrieve the original 31-bit signed int

    int v = (intptr_t)address >> 1;
    

For more information read


Another usage is when you pass a signed integer as void* which is usually done in simple callback functions or threads

void* my_thread(void *arg)
{
     intptr_t val = (intptr_t)arg;
     // Do something
}

int main()
{
    pthread_t thread1;
    intptr_t some_val = -2;
    int r = pthread_create(&thread1, NULL, my_thread, (void*)some_val);
}

1 When the implementation does arithmetic shift on signed types of course

2 Very new x86-64 CPUs may have UAI/LAM support for that

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • About using the high bits of pointers in X86_64. Do not do it. At any time the physical architecture can add another bit in order to use more RAM and if your code conflicts with that it is your problem. And I hope strongly the operating systems do absolutely nothing to help with this. All of the documentation mentions this. Treat those bits as if they're being used. – Zan Lynx Mar 20 '21 at 04:42
  • 2
    @ZanLynx the 64-bit address space is so huge that you can't almost ever use all of it (at least in the personal PCs). Just use from the top bits down to the lower ones and it'll be safe for the foreseeable future. Of course always prefer to use the lower bits, they'll always be safe – phuclv Mar 20 '21 at 04:57
  • @phuclv "640K ought to be enough for anybody" was said not so long ago. And look what we have now... So, I wouldn't be so sure. – sklott Jan 26 '23 at 05:58
  • 2
    @sklott [it's a myth](https://www.computerworld.com/article/2534312/the--640k--quote-won-t-go-away----but-did-gates-really-say-it-.html), he's never said it. And no this is completely different, 64-bit has more than 4 billion times the 32-bit address space, unlike the switch from x86's 20-bit address space (which the 640KB belongs to) to 32-bit that increased only 4096 times. It's impossible to count from 1 to 2⁶⁴ in your lifetime. And there are only ~10⁸⁰ particles in the universe so it's also impossible to provide the full 2⁶⁴-byte address space for every person – phuclv Jan 26 '23 at 10:02
  • @phuclv 64-16=48, i.e. it is only 256Tb, which sounds as big number, but not so much out of reach as you portray. 16 high bits is proposed to be used in this answer. – sklott Jan 26 '23 at 12:06
  • @sklott I already said that it's 64-7 = 57 with PML5, which means 128PB of address space. And it's 256TB (or TiB for the pedants), not 256Tb which is 8 times smaller. It's also thousands of times larger than the average RAM amount in a personal PC, you can't have that much RAM in your room – phuclv Jan 27 '23 at 00:39
  • Funny how so many people "think" the transition from 32bits to 64bits is the same as from 16bits to 32bits. First, some people forget it's exponential. But even when you assume that everything related to computers grows exponentially, people still forget that +32 is very different from +16! – MarcH Jul 15 '23 at 13:29