C语言 找到一种停止空指针解引用的方法

2admgd59  于 2023-10-16  发布在  其他
关注(0)|答案(3)|浏览(106)

我一直在试图弄清楚在C中的双向链表冒泡排序中,我在哪里解引用了一个空指针。当特别添加while(p2->next!=NULL)时,我得到Exception thrown: read access violation.p2->**next** was 0xFFFFFFFFFFFFFFFF.

node* swap(node* p1, node* p2) 
{
    p1->next = p2->next;
    p2->next->prev = p2->prev;//Exception thrown: read access violation.
    // p2->**next** was 0xFFFFFFFFFFFFFFFF.

    free(p2);
    return p1;
}
void sort_list(node** head, int n) {
    node** ptr = head;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            node* p1 = *ptr;
            node* p2 = p1->next;
            while (p2->next != NULL) {
                if (p1->data > p2->data) {
                    p1=swap(p1, p2);
                }
                else { p1 = p1->next;}
                
            }
        }
    }
}

有没有人可以给予我一个提示,如何解决这个问题?(也许reccomend的东西,我可以读了关于这个主题,因为我自己学习,我没有任何人来帮助我,当我卡住了)我也得到了一对夫妇的警告解除引用空指针在函数中创建头节点和其他函数,创建其余的节点

node* add_to_empty(node* head, int data)//creates the 1st node and returns head pointer to it
{
    node* temp = (node*)malloc(sizeof(node));
    temp->prev = NULL;
    temp->data = data;
    temp->next = NULL;
    head = temp;
    return head;
}
node* add_at_beg(node* head, int data)//adds node in front of the last one
{
    node* temp = (node*)malloc(sizeof(node));
    temp->prev = NULL;
    temp->data = data;
    temp->next = NULL;
    temp->next = head;
    head->prev = temp;
    head = temp;
    return head;}

通常如何避免这些警告?

kiz8lqtg

kiz8lqtg1#

我只想看看swap,因为这是你问的错误,因为那里有很多东西要解包。代码的其余部分也好不到哪里去,但我不得不把它排除在讨论范围之外。

正常情况

+-----+   +-----+   +-----+   +-----+   +-----+
head-->|     |-->|     |-->|     |-->|     |-->|     |-->NULL
NULL<--|     |<--|     |<--|     |<--|     |<--|     |<--tail
       +-----+   +-----+   +-----+   +-----+   +-----+
                    ^                   ^
                    |                   |
                    p1                  p2

为了交换p1和p2,我们需要更新

  • p1->prev和p1->next。
  • p2->prev和p2->next。
  • p1->prev->next.
  • p1->next->prev.
  • p2->上一页->下一页
  • p2->下一页->上一页

需要八个任务,但你只有两个!
我们还需要考虑边缘情况。有两个:

  • p1->prev为NULL,也就是说p1是列表的第一个节点。
  • p2->next是NULL,也就是说p2是列表的最后一个节点。

因为p1和p2总是不同的,并且因为p2在列表中总是晚于p1,所以p1->next和p2->prev都不会为NULL。
如果p1和p2是相邻的,有些赋值将是冗余的,但这不是特殊处理这种情况的理由。

p1是列表的第一个节点

+-----+   +-----+   +-----+   +-----+   +-----+
head-->|     |-->|     |-->|     |-->|     |-->|     |-->NULL
NULL<--|     |<--|     |<--|     |<--|     |<--|     |<--tail
       +-----+   +-----+   +-----+   +-----+   +-----+
          ^                             ^
          |                             |
          p1                            p2

为了交换p1和p2,我们需要更新

  • p1->prev和p1->next。
  • p2->prev和p2->next。
  • p1->prev->next head.
  • p1->next->prev.
  • p2->上一页->下一页
  • p2->下一页->上一页
    p2是列表的最后一个节点
+-----+   +-----+   +-----+   +-----+   +-----+
head-->|     |-->|     |-->|     |-->|     |-->|     |-->NULL
NULL<--|     |<--|     |<--|     |<--|     |<--|     |<--tail
       +-----+   +-----+   +-----+   +-----+   +-----+
                    ^                             ^
                    |                             |
                    p1                            p2

为了交换p1和p2,我们需要更新

  • p1->prev和p1->next。
  • p2->prev和p2->next。
  • p1->prev->next.
  • p1->next->prev.
  • p2->上一页->下一页
  • p2->next->prev tail.

请记住,这两种边缘情况可能发生在同一个调用中。

+-----+   +-----+   +-----+   +-----+   +-----+
head-->|     |-->|     |-->|     |-->|     |-->|     |-->NULL
NULL<--|     |<--|     |<--|     |<--|     |<--|     |<--tail
       +-----+   +-----+   +-----+   +-----+   +-----+
          ^                                       ^
          |                                       |
          p1                                      p2

为了交换p1和p2,我们需要更新

  • p1->prev和p1->next。
  • p2->prev和p2->next。
  • p1->prev->next head.
  • p1->next->prev.
  • p2->上一页->下一页
  • p2->next->prev tail.
cu6pst1q

cu6pst1q2#

指针取消引用通常是由这样的代码片段引起的:

node* p2 = p1->next;
while (p2->next != NULL)

如果p1->nextNULL呢?在这种情况下,您将NULL赋给p2,然后 * 立即使用表达式p2->next解除引用 *。
一个常用的模式来防止这类问题:

while (p2 != NULL && p2->next != NULL)

&&运算符的一个好处是它“短路”-如果左手表达式(p2 != NULL)的计算结果为false,则整个表达式的计算结果必须为false,因此 * 右手表达式不计算 *。换句话说,如果指针为NULL,则不会取消引用。
我并不是说这是您的特定用例的正确逻辑,但是当使用可能为NULL的指针时,此模式作为基本工具非常有用。

wfsdck30

wfsdck303#

你的舞蹈有很多问题。
swap()开始:

  • 你根本不需要一个普通的交换。你只需要一个swap-with-next,这可以更简单。
  • 你的交换不交换。如果第一个和第二个参数指向列表中的连续元素,它将从列表中删除第二个并释放它。这是不是你想要的,你正在使用这个功能。
  • 如果第一个和第二个参数没有指向列表中的连续元素,那么这个函数不仅会释放一个节点,而且还会破坏列表。
  • swap函数总是返回它的第一个参数。这本身并没有什么错,但它对你也没有任何用处。

关于sort_list()

  • 该函数的结构就像是执行 n 倍于它需要做的工作,因为它有一个额外的循环级别。
  • 链接列表函数通常不接受或依赖于预先指定的长度参数。如果你喜欢的话,我想你可以使用一个,但如果你这样做,那么你应该始终如一地这样做。
  • 您的while循环条件取决于p2->next,但p2->next不会在该循环中被修改,除非*p2可能被释放(见上文)。

老实说,那里没什么值得保存的。我会把它扔掉,从头开始。在此过程中考虑
1.通过临时给列表一个虚拟的头节点,可以在很大程度上避免特殊情况。框架:

void sort_list(node **head) {
    node dummy = { .next = *head };

    if (dummy.next) {  // else an empty list
        dummy.next->prev = &dummy;

        // ... perform the sort.  Every pass starts at dummy.next ...

        dummy.next->prev = NULL;
    }

    *head = dummy.next;
}

这样,您的真实的头节点就有了一个临时的前身。
1.正如已经暗示的那样,不要依赖于外部提供的列表长度。您可以通过维护一个指针来表示排序后的尾列表的头,并在到达当前尾列表的前一个时终止每个交换传递(然后成为尾列表的新头)。然后,您将知道,当真实的列表头(如果您实现(1),则为伪头的后继者)也是尾列表的头时,没有更多的传递要做。
1.代替一般的交换,实现一个swap_with_next(),这稍微简单一点。如果你实现了(1),那么swap_with_next()不需要考虑null前导的可能性,但是它仍然需要注意next->next是否为null。

相关问题