Robert Lu
Robert Lu

Reputation: 473

How can I set the rightmost bit of a pointer to 1 in C

I need to tag the address of an int pointer with a 1 in the rightmost bit and store that address somewhere. For example the address at 0x10 would become 0x11.

I have tried doing

int* addr = some_pointer;
int* tagged = (int*) (addr | 0x00000001);

but I get error: invalid operands to binary expression ('int *' and 'int'). How could I go about doing this?

Upvotes: 1

Views: 279

Answers (3)

chux
chux

Reputation: 153447

How can I set the rightmost bit of a pointer to 1?

C99/C11 provides optional integer types (u)intptr_t to covert a void* pointer to an integer and then back to a pointer. This round trip results in a pointer that is equal in value to the original. It may or may not have the same bit pattern as the original pointer.

#include <stdint.h>
int i = 42;
int* addr = &i;
void *vptr = addr;
uintptr_t uptr = (uintptr_t) vptr;

// Set least significant bit.
uintptr_t uptr1 = uptr | 1;
// or one-liner
uintptr_t uptr1 = ((uintptr_t) (void *) &i) | 1;

There is no print specifier for (u)intptr_t, so code can cast to the widest type for display purposes

printf("%p --> %ju --> %ju\n", vptr, (uintmax_t) uptr, (uintmax_t) uptr1); 
// or use macros from <inttypes.h>
printf("%p --> %" PRIuPTR " --> %" PRIuPTR "\n", vptr, uptr, uptr1); 

As long as OP only needs to store the value as an integer, no problem. (u)intptr_t values that originate from a valid pointer can convert back to a matching pointer type with no problem.

So far so good, yet OP may want to use the manipulated value.

Now the trick is that uptr1 may or may not covert to a valid void * pointer. Even if it does convert to a valid void * pointer, the conversion to an int * may or may not work. These two steps are beyond the C spec - undefined behavior.

// UB
void *vptr1 = (void *) uptr1;
printf("%p\n", vptr1); 
int *iptr1 = vptr1;

If code makes it this far, attempting to de-reference this new pointer, it is also UB.

printf("%d\n", *iptr1);       // UB, good luck

Upvotes: 7

lazyplayer
lazyplayer

Reputation: 393

As stated in comments, you need to convert the pointer to integral type explicitly:

int* addr = some_pointer;
int* tagged = (int*) ((uintptr_t)addr | 0x00000001);

Or shorter:

int* tagged = (int*) ((uintptr_t)some_pointer | 0x1);

You may need to include <stdint.h> in order to use macro define uintptr_t.


If you want to know why...

First, bit or operator | only allows Integral Types as operands.

A bit about the type categories of C in C89 draft. (I didn't check C99, C11)

  • Integral Type = char, signed and unsigned integer types, enumerated types.

  • Arithmetic Type = Integral Type and Floating Type

  • Scalar Type = Arithmetic Type and Pointer Type

So pointer is not an Integral Type. However, this is not enough to explain the error.

If there is an implicit conversion rule that automatically converts a pointer type to an integral type, you will be ok. (FYI: implicit conversion is a set of type conversion rules that will be applied to source automatically)

However, no such rule exists. So, you saw the error.

But sometimes, we really need to do math operations on a pointer variable. This is why we need explicit conversion.

Upvotes: 1

walley
walley

Reputation: 135

Simple way to do this that will likely annoy your compiler is to use address of operator int* ptr = &var;.

Below is my program:

#include <stdio.h>
int main(int argc, char *argv[]) {
    int val = 5;
    int* ptr = &val;
    int* addr = (int*)((int)&ptr | 0x00000001);
    printf("address val: %x, ptr: %x, &ptr: %x, addr: %x \n", &val, ptr, &ptr, addr);
    return 0;
}

The output of which is:

address val: 5880dacc, ptr: 5880dacc, &ptr: 5880dac0, addr: 5880dac1 

Upvotes: 0

Related Questions