william_grisaitis
william_grisaitis

Reputation: 5911

Why / when to use `intptr_t` for type-casting in C?

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!

Upvotes: 66

Views: 67177

Answers (5)

phuclv
phuclv

Reputation: 41794

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

Upvotes: 1

Christoph
Christoph

Reputation: 169583

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.

Upvotes: 13

Jens Gustedt
Jens Gustedt

Reputation: 78903

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.

Upvotes: 14

Chris Lutz
Chris Lutz

Reputation: 75399

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.

Upvotes: 48

Zan Lynx
Zan Lynx

Reputation: 54325

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.

Upvotes: 67

Related Questions