What Is the Use of Intptr_T

What is the use of intptr_t?

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.

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

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.

uintptr_t and intptr_t in C language

The XOR linked list method is a hack to construct a linked list that can be navigated in both directions using the space for a single pointer. The trick is to store the XOR of addresses of the next and previous items in the link member, converting these addresses as uintptr_t (or intptr_t) values, to perform bitwise exclusive or on integers of the appropriate size and store this info as an integer:

struct item {
uintptr_t link;
int data; // item payload. Can be any number of members of any type
};

The list can be traversed in both directions, provided you know the address of the previous (or the next) item:

struct item *get_link(struct item *p, const struct item *previous) {
return (struct item *)(p->link ^ (uintptr_t)previous);
}

To avoid a warning on alignement issues, you may need to add an extra cast as:

return (struct item *)(void *)(p->link ^ (uintptr_t)previous);

uintptr_t is an integer type that is specified as having the same size as void *, hence can contain all the information from any data pointer. Converting a data pointer to uintptr_t and back with casts should yield the same pointer.

intptr_t is the corresponding signed type, which is of little use per se.

The XOR linked list hack is mostly of historical interest today. The only advantage is a small size saving that is hardly worth the added complication. It is much better to use regular doubly linked lists if you need to scan the list in both directions. The scan with this trick requires keeping the pointer to both the current item an the previous one in the direction of traversal, whereas regular doubly linked lists can be handled with a single pointer, hence can be manipulated and/or shared in a much simpler fashion.

Here is a sample implementation:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

struct item {
uintptr_t link;
int data; // item payload. Can be any number of members of any type
};

struct xor_list {
struct item *head;
struct item *tail;
};

struct item *get_link(struct item *ip, const struct item *previous) {
return (struct item *)(ip->link ^ (uintptr_t)previous);
}

struct item *get_next(struct item *ip, struct item **previous) {
struct item *next = get_link(ip, *previous);
*previous = ip;
return next;
}

uintptr_t make_link(struct item *prev, const struct item *next) {
return (uintptr_t)prev ^ (uintptr_t)next;
}

struct item *add_item(struct xor_list *lp, int data) {
struct item *ip = malloc(sizeof(*ip));
if (ip) {
struct item *tail = lp->tail;
ip->data = data;
if (tail) {
struct item *prev = get_link(lp->tail, NULL);
ip->link = make_link(tail, NULL);
tail->link = make_link(prev, ip);
lp->tail = ip;
} else {
ip->link = make_link(NULL, NULL);
lp->head = lp->tail = ip;
}
}
return ip;
}

int main() {
struct xor_list list = { NULL, NULL };
struct item *ip, *prev;

add_item(&list, 1);
add_item(&list, 2);
add_item(&list, 3);
add_item(&list, 4);
add_item(&list, 5);

printf("traversing from head to tail:");
for (prev = NULL, ip = list.head; ip; ip = get_next(ip, &prev)) {
printf(" %d", ip->data);
}
printf("\n");

printf("traversing from tail to head:");
for (prev = NULL, ip = list.tail; ip; ip = get_next(ip, &prev)) {
printf(" %d", ip->data);
}
printf("\n");
return 0;
}

Using intptr_t instead of void*?

Is it a good idea to use intptr_t as a general-purpose storage (to hold pointers and integer values) instead of void*?

No.

intptr_t is not guaranteed to exist. First, as you note, it was introduced in C99. Second, implementations are not required to have an integer type big enough to hold converted pointer values without loss of information.

Converting an int to intptr_t and back is unlikely to lose information but there's no actual guarantee that intptr_t is wider than int.

If you want to store pointer values, store them in pointer objects. That's what pointer objects are for.

Any pointer to an object or incomplete type can be converted to void* and back again without loss of information. There is no such guarantee for pointers to functions -- but any pointer-to-function type can be converted to any other pointer-to-function-type and back without loss of information. (I'm referring to the C standard; I think POSIX provides some additional guarantees.)

If you want to store either an integer or a pointer value in the same object, the first thing you should do is re-think your design. If you've already done so, and concluded that you really do want to do this, consider using a union (and keeping careful track of what kind of value you've stored most recently).

There are APIs that use a void* argument to allow arbitrary data to be passed; see, for example, the POSIX pthread_create() function. This can be abused by casting an integer value to void* but it's safer to pass the address of an integer object.

Understanding void* against intptr_t and uintptr_t

The purpose of intptr_t and uintptr_t is that in some applications, you actually do need to do some sort of numeric computation on pointer values, perhaps by flipping individual bits, perhaps by XORing them, etc. In those cases, when you need to work with the numeric value of a pointer, intptr_t and uintptr_t are integer types that (if they exist) are guaranteed to be large enough to hold any pointer. This is not true of, say, int, since int's size relative to pointer sizes isn't specified.

Because it's fundamentally unsafe to do these conversions, C++ requires that you use reinterpret_cast to convert to and from intptr_t and uintptr_t and pointer types.

If all that you're doing is storing "a pointer to something," and provided that pointer isn't a function pointer or a member function pointer, you can just cast it to void*. That cast is guaranteed to work and the conversion from void* back to the original type only requires a static_cast and is guaranteed to be safe.

The size of intptr_t and uintptr_t isn't a good reason to avoid them. They're just for different applications. If you need to do numeric computations on pointers, use those types. Otherwise, if you just need to store "a pointer to something," use a void*.

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)

Does setting a void * value to a intptr_t variable require an explicit cast?

Summing up! Apologies in advance for any errors — please leave me a comment.

In C99:

  • Any pointer can be converted to an integer type.1
  • You might want to do that, e.g., if you are implementing your own operating system!
  • Conversions between pointers and integers can go horribly wrong,1 so are usually not what you want.
  • Therefore, the compiler warns you when you convert pointers to integers without casting. This is not overly pedantic, but to save you from undefined behaviour.
  • intptr_t (and uintptr_t, and likewise throughout) is just an integer type,2 so it is subject to the same risks as any other pointer-to-integer conversion. Therefore, you get the same warning.
  • However, with intptr_t, you at least know that the conversion from a pointer won't truncate any bits. So those are the types to use — with explicit casts — if you really need the integer values of pointers.

    • The spec1, #6 says that

      ... the
      result is implementation-defined. If the result cannot be represented in the integer type,
      the behavior is undefined.

      With intptr_t, the result can be represented in the integer type. Therefore, the behaviour is not undefined, but merely implementation-defined. That is (as far as I know) why those types are safe to use for receiving values from pointers.

Edit

Reference 1, below, is part of section 6.3, "Conversions." The spec says:3

Several operators convert operand values from one type to another automatically. This
subclause specifies the result required from such an implicit conversion...

and refers to section 6.5.4 for a discussion of explicit casts. Therefore, the discussion in Reference 1 indeed covers implicit casts from any pointer type to intptr_t. By my reading, then, an implicit cast from void * to intptr_t is legal, and has an implementation-defined result.1, 4

Regarding whether the explicit cast should be used, gcc -pedantic thinks it should, and there must be a good reason! :) I personally agree that the explicit cast is more clear. I am also of the school of thought that code should compile without warnings if at all possible, so I would add the explicit cast if it were my code.

References

1C99 draft (since I don't have a copy of the final spec), sec. 6.3.2.3 #5 and #6).

2Id., sec. 7.18.1.4

3Id., sec. 6.3

4Id., sec. 3.4.1, defines "implementation-defined behavior" as "unspecified behavior where each implementation documents how the choice is made." The implication is that the conversion is legal, but that the result may be different on one platform than on another.



Related Topics



Leave a reply



Submit