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

5

64

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!

Atlantis answered 13/6, 2011 at 3:16 Comment(2)
Yes, binging is a word, which originally meant to indulge in an activity, especially eating, to excess.Monad
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.Freeboard
P
49

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.

Perfidious answered 13/6, 2011 at 4:5 Comment(12)
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 longs vs ints significantly affect performance?Atlantis
@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.Perfidious
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.Atlantis
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.Atlantis
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.Perfidious
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!Atlantis
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.Taproot
@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.Grisly
@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.Bannon
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 garbageFreeboard
@Atlantis 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.Witty
@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.Witty
G
66

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.

Grisly answered 13/6, 2011 at 3:21 Comment(7)
It would be better to use uintptr_t. Signed types are messy and surely make no sense with pointers...Taproot
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?Atlantis
Speaking of always using intptr_t this answer claims that it is not widely available https://mcmap.net/q/162491/-using-intptr_t-instead-of-void . So what to do then?Beheld
@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 ;)Inspector
@Inspector that's not true, it's optional in C99Freeboard
@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 milleniaInspector
There's a remaining problem, which format string should you pass to printf to use intptr_t?Witty
P
49

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.

Perfidious answered 13/6, 2011 at 4:5 Comment(12)
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 longs vs ints significantly affect performance?Atlantis
@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.Perfidious
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.Atlantis
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.Atlantis
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.Perfidious
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!Atlantis
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.Taproot
@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.Grisly
@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.Bannon
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 garbageFreeboard
@Atlantis 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.Witty
@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.Witty
G
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.

Garish answered 13/6, 2011 at 9:29 Comment(3)
thanks. What are pointers, then, if not (binary) integers? Thank you for your time.Atlantis
@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.Garish
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.Taegu
I
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.

Irresolution answered 13/6, 2011 at 9:32 Comment(1)
Although this probably should have been a comment to the question.Waldenburg
O
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

Ophicleide answered 9/7, 2023 at 5:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.