60

I have a question regarding using intptr_t vs. long int. I've observed that incrementing memory addresses (e.g. via manual pointer arithmetic) differs by data type. For instance incrementing a char pointer adds 1 to the memory address, whereas incrementing an int pointer adds 4, 8 for a double, 16 for a long double, etc...

At first I did something like this:

char myChar, *pChar;
float myFloat, *pFloat;

pChar = &myChar;
pFloat = &myFloat;

printf( "pChar:  %d\n", ( int )pChar );
printf( "pFloat: %d\n", ( int )pFloat );

pChar++;
pFloat++;

printf( "and then after incrementing,:\n\n" );
printf( "pChar:  %d\n", (int)pChar );
printf( "pFloat:    %d\n", (int)pFloat );

which compiled and executed just fine, but XCode gave me warnings for my typecasting: "Cast from pointer to integer of different size."

After some googling and binging (is the latter a word yet?), I saw some people recommend using intptr_t:

#include <stdint.h>
...

printf( "pChar:  %ld\n", ( intptr_t )pChar );
printf( "pFloat: %ld\n", ( intptr_t )pFloat );

which indeed resolves the errors. So, I thought, from now on, I should use intptr_t for typecasting pointers... But then after some fidgeting, I found that I could solve the problem by just replacing int with long int:

printf( "pChar:  %ld\n", ( long int )pChar );
printf( "pFloat: %ld\n", ( long int )pFloat );

So my question is, why is intptr_t useful, and when should it used? It seems superfluous in this instance. Clearly, the memory addresses for myChar and myFloat were just too big to fit in an int... so typecasting them to long ints solved the problem.

Is it that sometimes memory addresses are too big for long int as well? Now that I think about it, I guess that's possible if you have > 4GB of RAM, in which case memory addresses could exceed 232 - 1 (max value for unsigned long ints...) but C was created long before that was imaginable, right? Or were they that prescient?

Thanks!

phuclv
  • 37,963
  • 15
  • 156
  • 475
william_grisaitis
  • 5,170
  • 3
  • 33
  • 40
  • 12
    Yes, binging is a word, which originally meant to indulge in an activity, especially eating, to excess. – ack Jul 04 '14 at 17:13
  • 2
    Using `intptr_t` with the printf functions is not portable, there is no format specifier for it. Cast the pointer to a void pointer instead and use the `%p` format specifier. – osvein Nov 19 '17 at 13:52

5 Answers5

65

intptr_t is a new invention, created after 64-bit and even 128-bit memory addresses were imagined.

If you ever need to cast a pointer into an integer type, always use intptr_t. Doing anything else will cause unnecessary problems for people who need to port your code in the future.

It took a long time to iron out all of the bugs with this in programs like Mozilla/Firefox when people wanted to compile it on 64-bit Linux.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • 23
    It would be better to use `uintptr_t`. Signed types are messy and surely make no sense with pointers... – R.. GitHub STOP HELPING ICE Jun 13 '11 at 05:10
  • Good call. Everything I see written about `uintptr_t` is in the context of c++ and the `inttypes.h` library. looks like it's in `stdint.h`, too, though. any difference? – william_grisaitis Jun 13 '11 at 05:30
  • 1
    Speaking of **always** using `intptr_t` this answer claims that it is not widely available http://stackoverflow.com/a/9492910/1073672 . So what to do then? – user10607 Dec 23 '14 at 07:11
  • 1
    @user10607: It is available from C99 onwards. That's more than a *decade*. So unless you're using an ancient compiler like TurboC or a very exotic platform with no int to hold a pointer, it *will* be available ;) – MestreLion Feb 17 '15 at 06:13
  • 2
    @MestreLion that's not true, it's optional in C99 – osvein Nov 19 '17 at 13:46
  • @Spookbuster true, but my point was it was _introduced_ in C99, almost _two decades_ ago. So availability should not be an issue, even if it was only an optional feature last millenia – MestreLion Nov 26 '17 at 01:53
  • There's a remaining problem, which format string should you pass to `printf` to use `intptr_t`? – Mark Ransom Jul 09 '23 at 14:28
48

Here's the thing: on some platforms, int is the right size, but on others, long is the right size. How do you know which one is the one you should use? You don't. One might be right, but the standard makes no guarantees about which one it would be (if it is either). So the standard provides a type that is defined to be the correct size, regardless of what platform you're on. Where before you had to write:

#ifdef PLATFORM_A
  typedef long intptr;
#else
  typedef int intptr;
#endif

Now you just write:

#include <stdint.h>

And it covers so many more cases. Imagine specializing the snippet above for every single platform your code runs on.

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • Hmm, thanks for the clarification. What if you just used `long` all the time? Wouldn't `long` work with shorter addresses as well? Does the extra memory allocation for `long`s vs `int`s significantly affect performance? – william_grisaitis Jun 13 '11 at 04:22
  • 2
    @caravaggisto - Nope. `long` is only guaranteed to be 32 bits, and may not work on a 64-bit platform. You could always use `long long`, but that doesn't exist before C99. I doubt the allocation matters much. It's more a problem of correctness. – Chris Lutz Jun 13 '11 at 04:27
  • Right, I see what you're saying with the 64-bit case. I was wondering, if you're only considering 32-bit systems, it seems like `long` would work on all, while `int` would not where '`long` is the right size' as you discuss in your initial answer. – william_grisaitis Jun 13 '11 at 04:33
  • That is, if you're on a system that has `int` length addresses, `long` would presumably work as well, no? It seems to be an issue of address lengths fitting within the range of a given data type. – william_grisaitis Jun 13 '11 at 04:36
  • It would work in that sense, but if you use a data type that's too big, perform arithmetic on it, and convert it back to a pointer, you may get incorrect results. `intptr_t` is designed to premit conversion of pointers to _and from_ integers. – Chris Lutz Jun 13 '11 at 04:45
  • Hmm didn't know that. Glad I stumbled upon `intptr_t`, then. I'm currently going through "Learn C on the Mac" by Dave Mark where so far typecasting has been done with just `int`. Granted I'm only 3/4 thru the book, and there's a section toward the end on typecasting, so perhaps this will be addressed in the text later. Thanks again for the response! – william_grisaitis Jun 13 '11 at 04:55
  • 2
    I would be very skeptical of any C book that teaches you to cast between pointers and integers. Actually almost all casts are indicative of bugs; they're at least a code smell. Pointer/integer casts are much worse. – R.. GitHub STOP HELPING ICE Jun 13 '11 at 05:12
  • 1
    @R..: Often an API which provides a callback function, or a messaging API like the Windows message pump, will have an integer data field or two that can be passed around. Your program may need to stuff a pointer into a `DWORD` or an `int` because that is all that is available in the API. – Zan Lynx Sep 05 '12 at 15:41
  • 3
    @ZanLynx WinAPI is a terrible API designed for horribly broken pre-standard C compilers in the mid-80s. It hasn't changed much since the Win16 era. There's a reason why MS is trying to kill it. – Jonathan Baldwin Mar 10 '14 at 22:28
  • Using `intptr_t` isn't any more portable than using `int` or `long` - it's not guaranteed to exist by C99. You also can't use it with printf. The real problem is you shouldn't be casting a pointer to an integer type. If an API requires you to do that, the API is garbage – osvein Nov 19 '17 at 13:53
  • 1
    @william_grisaitis even today Microsoft's compiler uses 32 bits for long and 64 bits for a pointer. They didn't increase the size of long because they wanted to maintain backward compatibility. – Mark Ransom Jul 09 '23 at 14:36
  • @ZanLynx in that situation a better solution is to use an arbitrary integer for the API and have a mapping from those integers to pointers. As a side benefit it allows you to know when e.g. the callback is to an object that has been deleted. – Mark Ransom Jul 09 '23 at 14:51
14

First, intptr_t is only for data pointers (not functions) and is not guaranteed to exist.

Then, no, you shouldn't use it for the purpose of printing. The %p is for that. You just have to cast your pointer to (void*) and there you go.

It is also no good for arithmetic / accessing individual bytes. Cast to (unsigned char*) instead.

intptr_t is really for the rare occasions that you have to interpret pointers as integers (which they really aren't). Don't that if you mustn't.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • thanks. What are pointers, then, if not (binary) integers? Thank you for your time. – william_grisaitis Jun 14 '11 at 16:57
  • 3
    @caravaggisto, this depends a lot on the architecture. Some architectures have segmented memory, so an address is composed of two parts. Just simple integer addition might for example not always give you a valid address. Arithmetic on `unsigned char` will do, provided you allocated a chunk that is large enough. – Jens Gustedt Jun 14 '11 at 18:17
  • `intptr_t` or `uintptr_t` could be useful for certain scenarios. For example you are building your own version of `malloc()`, a specialized dynamic memory allocator, perhaps for an embedded system or another computer system where a specialized memory allocator is needed. Say you keep track of deallocated blocks in an explicit free linked list. You could use an intptr_t to store pointers to the next and previous blocks in the linked list. This would not be part of the payload itself, but rather the previous word before it, in a "header" or "footer" of the block. – Galaxy Jul 04 '18 at 22:42
13

You could make your life easier by using the p conversion specifier:

printf("%p\n", (void *)foo);

Also, the portable way to print a variable of type (u)intptr_t is to use the PRI*PTR macros from inttypes.h; the following is equivalent to using p on my platform (32-bit):

printf("%08" PRIxPTR "\n", (uintptr_t)(void *)foo);

The casts to void * are necessary for full portability, but can be omitted on platforms with uniform pointer representations.

Christoph
  • 164,997
  • 36
  • 182
  • 240
0
printf( "pChar:  %ld\n", ( intptr_t )pChar );
printf( "pFloat: %ld\n", ( intptr_t )pFloat );

This is wrong. And this is even worse

printf( "pChar:  %ld\n", ( long int )pChar );
printf( "pFloat: %ld\n", ( long int )pFloat );

Is it that sometimes memory addresses are too big for long int as well?

Yes, nothing requires intptr_t to have the same size as long, for example on 64-bit Windows long is 32-bit but pointers are 64-bit, or some 128-bit architectures in the future may have 128-bit pointers but shorter long type. Therefore this is incorrect

Clearly, the memory addresses for myChar and myFloat were just too big to fit in an int... so typecasting them to long ints solved the problem

It only solves the problem on some platforms. You must use PRIdPTR or PRIiPTR for intptr_t. The correct way is like this

printf("pChar:  %" PRIiPTR "\n", (intptr_t)pChar);
printf("pFloat: %" PRIdPTR "\n", (intptr_t)pFloat);

But why do you need to print the decimal values of the pointers? Usually they're supposed to be printed as hex. Besides uintptr_t is preferred in most cases, especially when doing bitwise arithmetic on pointers (for example for tagged pointers or XOR linked list) so this is better

printf("pChar:  %" PRIxPTR "\n", (uintptr_t)pChar);
printf("pFloat: %" PRIxPTR "\n", (uintptr_t)pFloat);

why is intptr_t useful, and when should it used

There are very few cases where you need intptr_t rather than uintptr_t. See What is the use of intptr_t? for some examples

phuclv
  • 37,963
  • 15
  • 156
  • 475