虽然保留一个指向列表头部和尾部的数组(或就此而言是一个指针)没有任何问题,但如果您使用数组,则在分配地址后保持您的数组引用您的列表操作。将&array[x] 与您的列表操作混合只会造成混乱。使用列表时,将其视为列表并忘记数组。
您的主要问题是您将一个节点迭代到远处寻找插入new_node 的位置,导致您在停止之前迭代到tail。在插入new_node 之前的节点上停止迭代。你可以通过测试来做到这一点:
/* test curr->qnext->data > key to stop before tail */
while (curr->qnext && curr->qnext->data > key)
curr = curr->qnext;
(注意: 使用变量屏蔽间接级别,就像您接下来使用 prev = curr->qprev; 所做的那样,只是隐藏了细节——这可能会在以后增加混乱。这是完全合法的,但请谨慎使用。 ..)
现在您可以集中精力在&head 和&tail 之间插入new_node 需要插入的位置。
在任何列表插入中,您只需重新连接当前节点的指针->next 指向new_node 和下一个节点的指针->prev 指向new_node。要完成插入,您的new_node->qprev 指向curr 和new_node->qnext 指向curr->next,例如
new_node->qprev = curr; /* rewire pointers */
new_node->qnext = curr->qnext;
curr->qnext->qprev = new_node;
curr->qnext = new_node;
(注意:最简单的方法是拿出一张纸和一支 2 号铅笔,然后画一个方块 curr 一个方块new_node 和 tail 的块,然后为 prev/next 指针画线(对于没有 new_node 的列表和带有它的列表)。然后,按照逻辑,坐下来键盘和啄出来。)
此外,您必须始终验证您的分配,例如
/* allocate and VALIDATE! */
if (!(new_node = malloc (sizeof *new_node))) {
perror ("malloc - new_node");
exit (EXIT_FAILURE);
}
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放。因此,如果您分配它,请跟踪指向该块的指针和free,当您完成它时。例如,当完成输出列表值(或在专用循环中)时,您可以释放分配的内存,类似于:
curr = &head; /* output list */
while (curr) {
printf ("%d\n", curr->data);
struct Node *victim = curr; /* self-explanatory */
curr = curr->qnext;
/* do not forget to free allocated memory */
if (victim != &head && victim != &tail) {
free (victim);
}
}
总而言之,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
struct Node {
int data;
struct Node *qprev;
struct Node *qnext;
} Node;
struct Node qllentry[2];
int main (void) {
struct Node head = { .data = INT_MAX },
tail = { .data = INT_MIN },
*curr,
*new_node;
qllentry[0] = head; /* keep your array and list operations separate */
qllentry[1] = tail;
head.qnext = &tail; /* begin list operations */
tail.qprev = &head;
int key = 20;
curr = &head;
/* test curr->qnext->data > key to stop before tail */
while (curr->qnext && curr->qnext->data > key)
curr = curr->qnext;
/* allocate and VALIDATE! */
if (!(new_node = malloc (sizeof *new_node))) {
perror ("malloc - new_node");
exit (EXIT_FAILURE);
}
new_node->data = key; /* assign value to new_node */
new_node->qprev = curr; /* rewire pointers */
new_node->qnext = curr->qnext;
curr->qnext->qprev = new_node;
curr->qnext = new_node;
curr = &head; /* output list */
while (curr) {
printf ("%d\n", curr->data);
struct Node *victim = curr; /* self-explanatory */
curr = curr->qnext;
/* do not forget to free allocated memory */
if (victim != &head && victim != &tail) {
free (victim);
}
}
return 0;
}
使用/输出示例
$ ./bin/llarray
2147483647
20
-2147483648
内存使用/错误检查
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/llarray
==8665== Memcheck, a memory error detector
==8665== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8665== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==8665== Command: ./bin/llarray
==8665==
2147483647
20
-2147483648
==8665==
==8665== HEAP SUMMARY:
==8665== in use at exit: 0 bytes in 0 blocks
==8665== total heap usage: 1 allocs, 1 frees, 24 bytes allocated
==8665==
==8665== All heap blocks were freed -- no leaks are possible
==8665==
==8665== For counts of detected and suppressed errors, rerun with: -v
==8665== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
简单的指针转储/检查
最后,除了使用调试器单步执行地址之外,您还可以随时编写简短的调试路由来帮助您找出指针处理是否以及在何处出现问题。 (你根本不需要输出任何东西,如果你愿意,你可以检查地址是否相等)这让你可以一次查看所有指针。输出节点指针的简单路由通常很有帮助。你所需要的只是,例如
void debugptrs (struct Node *list)
{
printf ("list pointers:\n\n");
for (struct Node *iter = list; iter; iter = iter->qnext)
printf ("prev: %16p curr: %16p next: %16p\n",
(void*)iter->qprev, (void*)iter, (void*)iter->qnext);
putchar ('\n');
}
这将提供类似于以下内容的输出:
$ ./bin/llarray
list pointers:
prev: (nil) curr: 0x7ffd56371910 next: 0x1038010
prev: 0x7ffd56371910 curr: 0x1038010 next: 0x7ffd56371930
prev: 0x1038010 curr: 0x7ffd56371930 next: (nil)
我总是发现从头到尾从视觉上遍历地址是很有帮助的。如果某个节点的任何 prev 或 next 不是上一行(或下一行)该节点的地址输出,那么您就知道问题出在哪里。
查看一下,如果您还有其他问题,请告诉我。