【问题标题】:Using XOR with pointers in C在 C 中对指针使用 XOR
【发布时间】:2014-12-21 13:53:41
【问题描述】:

上周,我们的老师给了我们一个作业,在 C 语言中创建一个双链表,而不在结构中使用两个指针;我们必须使用一个指针来实现它,以指向列表中的下一个和上一个节点。我确信这样做的唯一方法是使用 XOR 来合并下一个和上一个方向,然后指向那个“混合”内存分配,如果我需要 prev 或 next 的方向,我可以再次使用 XOR 来获取我需要的内存值之一。

我设计了算法,我认为它可以工作,但是当我尝试实施解决方案时,我遇到了一个问题。当我尝试编译程序时,编译器告诉我不能对指针使用 XOR (^):

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

这里是在列表前面添加一个节点的函数:

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.
  }
}

我在 C++ 中读到过,指向指针的 XOR 是有效的。关于如何在 C 中实现这一点的任何想法?我还在某处读到需要使用intptr_t,但我不明白如何处理它。

【问题讨论】:

  • @Affess 我认为 OP 知道算法是如何工作的,只是不知道实现它的确切 C 构造。
  • .-> 运算符绑定得非常紧密;在它们周围留出空间是一种惯例。始终使用root->px 等。
  • 对于 googlers,从声明 myType * ptr = (myType *)(((void *)a) ^ ((void *)b)); 错误 VS 2017 编译器为 error C2296: '^': illegal, left operand has type 'void *error C2297: '^': illegal, right operand has type 'void *'

标签: c list pointers xor


【解决方案1】:
#include <stdint.h>

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

从技术上讲,C 不要求void * 能够存储uintptr_t 的任何值;由于值(uintptr_t)p1 ^ (uintptr_t)p2(我们称之为X)实际上并不是有效指针到uintptr_t的转换,因此(void *)X回到uintptr_t的实现定义的转换可能不会产生值X ,打破你想做的一切。

幸运的是,通过使用 uintptr_t 而不是 void * 类型的对象来存储您的“异或指针”,这很容易解决。只需这样做:

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

然后您可以稍后通过以下方式安全地从p2 恢复p1(反之亦然):

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

由于 xor_ptr ^ (uintptr_t)p2 等于(uintptr_t)p1,C 对uintptr_t 的定义保证了转换为void * 的这个值等于(作为指针)@987654340 @(根据 C11 7.20.1.4)。

【讨论】:

  • 谢谢你,这就像一个魅力。但我真的不明白为什么它会起作用,你能解释一下吗?
  • uintptr_t 是一个整数类型(在其上定义了^,而不是违反约束)能够表示任何指针。因此,转换为uintptr_t,在那里进行算术运算,然后转换回指针即可解决您的问题。
  • C 没有定义对 uintptr_t 的按位操作也会返回原始指针: 以下类型指定一个无符号整数类型,其属性是任何指向 void 的有效指针都可以转换成这个类型,然后转换回指向void的指针,结果会和原来的指针比较: 这个xor是ub。它之所以有效,是因为我们了解架构,但在未来它可能会导致与严格别名相似的问题。
  • @2501: xor 不是 UB。将结果转换回类型void * 是实现定义的,因此存在void * 可能无法忠实地表示值((uintptr_t)p1 ^ (uintptr_t)p2) 的问题(以一种往返方式返回到您可以使用的东西从p1 恢复原来的p2,反之亦然),这是我的回答中的一个技术缺陷。但是,可以通过将“xor 指针”存储为uintptr_t 而不是void * 并仅在执行第二次xor 以取回原始值后转换回void * 来解决此问题。
  • by "xor is ub" 我指的是答案中提到的整个操作。评论开头就暗示了这一点。 (显然定义了对无符号整数的异或。)将 void 指针存储为 uintptr_t 似乎是一个聪明的解决方案,但它并不能解决问题。修改 uintptr_t 后,即使它具有相同的值,也不会定义转换回 void。
【解决方案2】:

您可以使用reinterpret_cast 对指针进行异或运算。请注意,不建议这样做,但它确实有效。

#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;
}

【讨论】:

    【解决方案3】:

    在这里,我举了一个例子来说明如何使用双链接 XOR 列表,该列表的节点在单个 int 变量中存储指向前一个节点和下一个节点的指针: 我在这里显式使用裸指针,以更好地展示访问机制。

    #include<cassert>
    
    using namespace std;
    
    struct ListNode
    {
        int val;
        int 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 ^ (reinterpret_cast<int>(newNode)));
                int pnode = reinterpret_cast<int>(last);
                last = newNode;
                last->both = pnode;
            }
            size++;
        }
    
        int get(unsigned idx)
        {
            if (idx >= size)
                throw("Out of bounds.");
    
            ListNode* cur = head;
            unsigned i = 0;
            int pnode = 0;
            while (cur != nullptr && i < idx)
            {
                int nextNode = (pnode ^ cur->both);
                pnode = reinterpret_cast<int>(cur);
                cur = reinterpret_cast<ListNode*>(nextNode);
                i++;
            }
            return cur->val;
        }
    
        ~XORList()
        {
            ListNode* cur = head;
            int pnode = 0;
            while (cur != nullptr)
            {
                int nextNode = (pnode ^ cur->both);
                pnode = reinterpret_cast<int>(cur);
                delete cur;
                cur = reinterpret_cast<ListNode*>(nextNode);
            }
        }
    };
    
    int main()
    {
        XORList xorList;
        xorList.addNode(1);
        xorList.addNode(2);
        xorList.addNode(3);
        assert(xorList.get(0) == 1);
        assert(xorList.get(1) == 2);
        assert(xorList.get(2) == 3);
    }
    

    【讨论】:

      猜你喜欢
      • 2014-06-04
      • 2012-07-15
      • 1970-01-01
      • 1970-01-01
      • 2012-02-25
      • 1970-01-01
      • 2022-11-14
      • 1970-01-01
      • 2020-09-02
      相关资源
      最近更新 更多