9

Last week, our teacher gave us an assignment to make a double linked list in C without using two pointers in the struct; we have to implement it just using one pointer to point to the next and previous node on the list. I'm convinced that the only way of doing so is using XOR to merge the next and previous direction, and then point to that "hybrid" memory allocation, and if I need the directions of prev or next, I can use XOR again to get one of the memory values I need.

I designed the algorithm and I thought it was going to work, but when I tried to implement the solution, I encountered with a problem. When I tried to compile the program, the compiler told me that I couldn't use XOR (^) to pointers:

invalid operands to binary ^ (have ‘void *’ and ‘node *’)

Here is the function to add a node at the front of the list:

typedef  struct node_list{
  int data;
  struct node_list *px;
}  node;

node* addfront ( node *root, int data ){ 
  node *new_node, *next;

  new_node = malloc ( sizeof ( node )); 
  new_node -> data = data;

  new_node -> px = (NULL ^ root);//this will be the new head of the list

  if ( root != NULL ){           // if the list isn't empty
    next = ( NULL ^ root -> px );  // "next" will be the following node of root, NULL^(NULL^next_element).
    root = ( new_node ^ next );    //now root isn't the head node, so it doesn't need to point null.
  }
}

I read that in C++, XOR to pointers is valid. Any ideas of how could implement this in C? I also read somewhere that I needed to use intptr_t, but I didn't understand what to do with it.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
angel208
  • 281
  • 1
  • 5
  • 15
  • 1
    possible duplicate of [How exactly does a XOR Linked list work?](http://stackoverflow.com/questions/16138998/how-exactly-does-a-xor-linked-list-work) – Afforess Oct 26 '14 at 04:15
  • @Afforess I think the OP knows how the algorithm works, just not the exact C constructs to implement it. – Fred Foo Oct 26 '14 at 13:31
  • The `.` and `->` operators bind extremely tightly; it is aconventional to leave space around them. Use `root->px` etc always. – Jonathan Leffler Oct 26 '14 at 13:37
  • For googlers, from statement `myType * ptr = (myType *)(((void *)a) ^ ((void *)b));` errors the VS 2017 compiler as `error C2296: '^': illegal, left operand has type 'void *` and `error C2297: '^': illegal, right operand has type 'void *'`. – JamesThomasMoon Feb 22 '19 at 06:14

3 Answers3

21
#include <stdint.h>

(void *)((uintptr_t)p1 ^ (uintptr_t)p2)

Technically, C does not mandate that void * be able to store any value of uintptr_t; since the value (uintptr_t)p1 ^ (uintptr_t)p2 (let's call it X) is not actually the conversion to uintptr_t of a valid pointer, the implementation-defined conversion of (void *)X back to uintptr_t may not produce the value X, breaking everything you want to do.

Fortunately, this is easily solved by using objects of type uintptr_t rather than void * to store your "xor pointers". Simply do:

uintptr_t xor_ptr = (uintptr_t)p1 ^ (uintptr_t)p2;

and then you can safely recover p1 from p2 (or vice versa) later via:

(void *)(xor_ptr ^ (uintptr_t)p2)

Since the value xor_ptr ^ (uintptr_t)p2 is equal to (uintptr_t)p1, C's definition of uintptr_t guarantees that this value, converted to void *, is equal (as a pointer) to p1 (per C11 7.20.1.4).

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • thank you, that worked like a charm. but i dont really understand why does it works, could you explain it? – angel208 Oct 26 '14 at 15:37
  • 1
    `uintptr_t` is an integer type (on which `^` is defined and not a constraint violation) capable of representing any pointer. So casting to `uintptr_t`, doing the arithmetic there, and casting back to a pointer solves your problem. – R.. GitHub STOP HELPING ICE Oct 27 '14 at 01:55
  • C doesn't define that a bitwise operation on the uintptr_t will then also yield back the original pointer: *The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:* This xor is ub. It works because we know the architecture, but in the future it may cause similar problems as strict alising did. – 2501 Aug 20 '16 at 07:23
  • @2501: The xor is not UB. Converting the result back to type `void *` is implementation-defined, so there is a problem that `void *` may not be able to represent the value `((uintptr_t)p1 ^ (uintptr_t)p2)` faithfully (in a way that round-trips back to something you can use to recover the original `p2` from `p1` or vice versa), and this is something of a technical flaw in my answer. It can however be solved by storing your "xor pointers" as `uintptr_t` rather than as `void *` and only converting back to `void *` after performing the second xor to get back the original value. – R.. GitHub STOP HELPING ICE Aug 20 '16 at 16:21
  • @2501: As to *why* the xor is not UB, `uintptr_t` is an unsigned integer type, and `x^y` is completely well-defined (no trap representations, no overflow, etc.) for any pair of unsigned integer type operands `x` and `y`. – R.. GitHub STOP HELPING ICE Aug 20 '16 at 16:23
  • 1
    by "xor is ub" I was referring to the whole operation mention in the answer. That was implied in the beginning of the comment. (Obviously xor on unsigned integers is defined.) Storing void pointers as uintptr_t seems like a clever solution, but it doesn't solve the problem. After uintptr_t is modified the conversion back to void simply isn't defined even if it has the same value. – 2501 Aug 20 '16 at 16:36
  • You might say that is nonsense since in practice they have the same value right? Sure, but the compiler may see the operation, determine it isn't defined and optimize it out, just like checks like these are removed: `if( signed_x > signed_x + 1 )` – 2501 Aug 20 '16 at 16:55
  • I concede that the behavior is in fact implementation-defined, because of 6.3.2.3 p5, which I forgot to include in my reasoning. So you were right. Perhaps consider including this in the rather dry answer. – 2501 Aug 20 '16 at 17:04
  • @2501: If it has the same value, conversion back certainly is defined. C defines conversion of *values* not objects. The same value converted twice produces the same result. For unsigned `x` (so we don't have to worry about overflow), `(T)(x+1-1)` is always the same as `(T)x`. Same applies for xor, etc. – R.. GitHub STOP HELPING ICE Aug 20 '16 at 17:06
  • The reference to signed overflow check was an example how undefined behavior manifests itself, and it wasn't directly related to xor, uintptr_t and pointers. I think that is rather obvious. See the above comment which you might have missed for the rest. Without the rule 6.3.2.3 p5, the behavior would be undefined and the compiler could simply remove the call just like it can remove signed overflow checks(see above) which are undefined. – 2501 Aug 20 '16 at 17:09
  • @2501: It's not 6.3.2.3p5 but rather 7.20.1.4 which is relevant. If `uintptr_t` is defined, it "designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer". – R.. GitHub STOP HELPING ICE Aug 20 '16 at 17:12
  • @R.. It's definitely 6.3 because 7.2 only talks about conversion there and back without modification. 6.3 talks about any integer, which includes uintptr_t after it is modified. – 2501 Aug 20 '16 at 17:15
  • @2501: You're right that 6.3.2.3p5 is relevant to the case in my original answer as written; it makes it so that it's implementation-defined whether that form of the answer works. But if you just store the xor pointer as `uintptr_t` and never convert it to `void *`, 7.20.1.4 is the relevant text. No modified `uintptr_t` is ever converted to a pointer type; only the original value is round-tripped back to a pointer. – R.. GitHub STOP HELPING ICE Aug 20 '16 at 22:53
1

You can do XOR on pointers using reinterpret_cast. Note, it is NOT recommended, but it works.

#include<iostream>
using namespace std;

int main()
{
    int* x = new int(1);
    int* y = new int(2);

    cout << *x << ", " << *y << endl; // outputs 1, 2

    int addr1 = reinterpret_cast<int>(x);
    int addr2 = reinterpret_cast<int>(y);
    int addr3 = (addr1 ^ addr2); // xor on both pointers

    int* ptr = reinterpret_cast<int*>(addr3 ^ addr2); // xor "away" *y, ptr points on x now
    *ptr = 5;
    cout << *x << endl; // 5

    ptr = reinterpret_cast<int*>(addr3 ^ addr1); // xor "away" *x, ptr points on y now
    *ptr = 6;
    cout << *y << endl; // 6

    delete x;
    delete y;
    return 0;
}
Andrushenko Alexander
  • 1,839
  • 19
  • 14
0

Here I make an example of how a double linked XOR list could be used, a list with nodes that store pointer to both, previous and next node in single int variable: I use here naked pointers explicit, to better demonstrate mechanics of accesses.

#include<cassert>

using namespace std;

struct ListNode
{
    int val;
    uint64_t both;
    ListNode(int v) : val(v), both(0) {}
};

struct XORList
{
    ListNode* head;
    ListNode* last;
    unsigned size;
    XORList() : head(nullptr), last(nullptr), size(0) {}

    void addNode(int x)
    {
        if (head == nullptr)
        {
            head = new ListNode(x);
            last = head;
        }
        else
        {
            ListNode* newNode = new ListNode(x);
            last->both = (last->both ^ (uint64_t ) newNode);
            uint64_t prev_node = (uint64_t) last;
            last = newNode;
            last->both = prev_node;
        }
        size++;
    }

    int get(unsigned idx)
    {
        if (idx >= size){
            throw("Out of bounds.");
        }

        ListNode* cur = head;
        unsigned i = 0;
        uint64_t prev_node = 0;
        while ((cur != nullptr) && (i < idx))
        {
            uint64_t nextNode = (prev_node ^ cur->both);
            prev_node = (uint64_t) cur;
            cur = (ListNode*) nextNode;
            i++;
        }
        return cur->val;
    }

    ~XORList()
    {
        ListNode* cur = head;
        uint64_t prev_node = 0;
        while (cur != nullptr)
        {
            uint64_t nextNode = (prev_node  ^ cur->both);
            prev_node  = (uint64_t) cur;
            delete cur;
            cur = (ListNode*) nextNode;
        }
    }
};

int main()
{
    XORList xorList;
    xorList.addNode(1);
    xorList.addNode(2);
    xorList.addNode(3);
    xorList.addNode(4);
    assert(xorList.get(0) == 1);
    assert(xorList.get(1) == 2);
    assert(xorList.get(2) == 3);
    assert(xorList.get(3) == 4);
}
Andrushenko Alexander
  • 1,839
  • 19
  • 14