Search code examples
cpointersbitwise-operatorsnonblockingcompare-and-swap

How to "mark" pointers by setting the last bit to 1?


I am trying to mark and unmark pointers so I can implement Non-Blocking linked lists. I checked that on my architecture the last bit is never used, so I am trying to use change it to mark/unmark pointers.

I am trying to perform an OR to set the last bit to 1, an AND to unset it and an AND to check if it is set to 1. The problem is that when I perform bitwise (the commented Macros) operations on a pointer I cannot dereference it. Dereferencing it results in a segmentation fault even though the integer value of the pointer is correct.

More specifically, the #define unmark(x) (x & (uintptr_t) 0xfffffffe) is what is causing the segmentation fault. If I do not use it (and use #define unmark(x) x - 1 instead) the program works.

Incrementing and decrementing the pointer seems to be working, but it may make the solution architecture specific. This is because on my architecture pointers always end in 8, which has the final bit set to 0. If this is not the case my solution would not be very portable.

I understand that manipulating pointers is probably not portable anyway, but it is required for this algorithm. If someone knows what is causing the problem it would be fantastic.

This is the code I have used to test the solution:

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

//This produces segfault, for some reason
//#define unmark(x)     (x & (uintptr_t) 0xfffffffe)
//#define mark(x)   (x | (uintptr_t) 0x00000001)
#define is_marked(x)    ((long) x & 0x00000001)

#define mark(x)     x + 1
#define unmark(x)   x - 1

struct Example {
     long x;
     long y;
};

int main() {
    struct Example *x = malloc(sizeof(struct Example));
    x->x = 10;
    x->y = 20;
    uintptr_t p = (uintptr_t)(void*) x;

    printf("%ld\n", ((struct Example *) (void*) p)->y);

    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    p = mark(p);
    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    p = unmark(p);
    printf("%04x\n", p);
    printf("Is marked: %d\n", is_marked(p));

    printf("%ld\n", ((struct Example *) (void*) p)->y);

    return 0;
}

Solution

  • Code has many problems

    Not clearing just the least significant bit

    x & (uintptr_t) 0xfffffffe assumes uintptr_t is 32-bit. Better as

     #define unmark(x)  ((x) & ~(uintptr_t)1)
     // or 
     #define unmark(x)  (((x) | 1) ^ 1)
    

    Assuming bit manipulation of pointers is OK

    Mis-matched specifier

    // printf("%04x\n", p);
    printf("%04jx\n", (uintmax_t) p);
    
    // printf("Is marked: %d\n", is_marked(p));
    // Unclear correct specifier.
    

    Better as

    // #define is_marked(x)    ((long) x & 0x00000001)
    #define is_marked(x)    (!!((x) & 1))
    

    Unneeded casts

    //#define mark(x)   (x | (uintptr_t) 0x00000001)
    #define mark(x)   ((x) | 1)