因此,为了巩固我对C中指针和内存管理的理解,我决定对链表进行基本实现。
我对我的代码中的某些内容非常怀疑,并且想知道我是否可以得到一些澄清。首先,代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//Doubly-linked lists brah
typedef struct ll_int {
struct ll_int* prev;
int value;
struct ll_int* next;
} ll_int_t;
ll_int_t* newNode(int value) {
//Allocate the memory
ll_int_t* new = (ll_int_t*) malloc(sizeof(ll_int_t));
//If it was not successfully created, exit
if (new == NULL) {
fprintf(stderr, "Unable to allocate memory for new LL node\n");
exit(EXIT_FAILURE);
}
//Set up the node
new->value = value;
new->prev = NULL;
new->next = NULL;
return new;
}
void addElement(ll_int_t* head, int value) {
//Check if the head is in fact the end of the list
if (head->next == NULL) {
ll_int_t* new = newNode(value);
head->next = new;
new->prev = head;
} else {
addElement(head->next, value);
}
}
int lenList(ll_int_t* head) {
int i = 1;
ll_int_t* curr = head;
while ((curr = curr->next) != NULL) i++;
free(curr);
return i;
}
void delHead(ll_int_t** list) {
if (*list != NULL && lenList(*list) > 1) {
ll_int_t* dead = *list;
*list = (*list)->next;
(*list)->prev = NULL;
free(dead);
}
}
bool delElementAt(ll_int_t** list, int pos) {
if (pos == 0) {
delHead(list);
return true;
} else {
//TODO: Implement
}
}
void printLL(ll_int_t** list) {
//We could be clever and traverse back to the start if we're not
//given the head of the list... but that's /effort/
if ((*list)->prev != NULL) {
printf("That is not the head of the Linked List!\n");
return;
}
int i = 0;
ll_int_t* curr = *list;
do {
printf("(%d): %d\n", i++, curr->value);
} while((curr = curr->next) != NULL); //sneaky
free(curr);
}
int main (int argc, char** argv) {
ll_int_t* head = newNode(5);
addElement(head, 10);
printf("lenList: %d\n", lenList(head));
addElement(head, 15);
printf("lenList: %d\n", lenList(head));
addElement(head, 20);
printf("lenList: %d\n", lenList(head));
printLL(&head);
delElementAt(&head, 0);
printLL(&head);
return(EXIT_SUCCESS);
}
让我挑选一些痛点:
所以最初,我的addElement
函数看起来像:
void addElement(ll_int_t* head, int value) {
//Attempt to create the new node
ll_int_t* new = newNode(value);
//Check if the head is in fact the end of the list
if (head->next == NULL) {
head->next = new;
new->prev = head;
} else {
ll_int_t* curr = head->next;
while (curr->next != NULL) curr = curr->next;
curr->next = new;
new->prev = curr;
}
}
但我担心ll_int_t* curr
,因为它感觉像是一个不必要的副本。所以我把它改为使用尾递归:
void addElement(ll_int_t* head, int value) {
//Check if the head is in fact the end of the list
if (head->next == NULL) {
ll_int_t* new = newNode(value);
head->next = new;
new->prev = head;
} else {
addElement(head->next, value);
}
}
这让我感到更快乐,但我想我的问题是,我真的在这里取得了什么成就吗?
此外,free()
和lenList
中的printLL
实际上是否必要,或者功能范围是否为我处理此问题?
答案 0 :(得分:4)
如果您之前没有使用valgrind
工具,这是一个学习如何自信地调试内存泄漏的绝佳机会。去安装valgrind
。它会告诉你是否有内存泄漏。
[~]$ valgrind --leak-check=full ./test
==12880== Memcheck, a memory error detector
==12880== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==12880== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==12880== Command: ./test
==12880==
lenList: 2
lenList: 3
lenList: 4
(0): 5
(1): 10
(2): 15
(3): 20
(0): 10
(1): 15
(2): 20
==12880==
==12880== HEAP SUMMARY:
==12880== in use at exit: 72 bytes in 3 blocks
==12880== total heap usage: 4 allocs, 1 frees, 96 bytes allocated
==12880==
==12880== 72 (24 direct, 48 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==12880== at 0x4C27BE3: malloc (vg_replace_malloc.c:299)
==12880== by 0x4006F1: newNode (test.c:14)
==12880== by 0x400764: addElement (test.c:31)
==12880== by 0x40094B: main (test.c:89)
==12880==
==12880== LEAK SUMMARY:
==12880== definitely lost: 24 bytes in 1 blocks
==12880== indirectly lost: 48 bytes in 2 blocks
==12880== possibly lost: 0 bytes in 0 blocks
==12880== still reachable: 0 bytes in 0 blocks
==12880== suppressed: 0 bytes in 0 blocks
==12880==
==12880== For counts of detected and suppressed errors, rerun with: -v
==12880== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
答案 1 :(得分:2)
现在你最大的问题不是泄密,而是free
- 垃圾邮件。只有在修复之后才会有泄漏,然后你可以在正确的位置修复它。
您的代码中有两个位置不正在销毁链接列表中的节点,并且您应该在一个地方,但不方便地丢失。通过在适当的地方使用const-ness可以更好地检测前两个。例如:
int lenList(ll_int_t* head) {
int i = 1;
ll_int_t* curr = head;
while ((curr = curr->next) != NULL) i++;
free(curr); // ????????
return i;
}
这是一个简单的(尽管效率低下)长度计算功能。不应以任何方式修改列表本身。因此,输入指针指向const。
int lenList(const ll_int_t *head) {
int i = 1;
ll_int_t* curr = head; // Warning: const-ness disqualified
while ((curr = curr->next) != NULL) i++;
free(curr);
return i;
}
现在当你尝试编译它时,你会得到至少一个警告,告诉你在分配给非常量指针时head
的常量已被丢弃({ {1}})。编译时将警告视为错误(您应该 始终 执行此操作)将因此对此进行红色标记。而且,fyi,这个功能可以大大简化为:
curr
接下来是int lenList(const ll_int_t *head)
{
int i = 0;
for (; head; ++i, head = head->next);
return i;
}
函数,它的功能更加相同。
printLL
这是与以前相同的问题,这里提到的所有内容都适用于此,包括简化。此外,该功能通过地址不必要地传递头指针,这最终是没有意义的。就像以前一样,只需要一个简单的指向const的指针,它可以在不影响调用者的情况下自由修改:
void printLL(ll_int_t **list) {
//We could be clever and traverse back to the start if we're not
//given the head of the list... but that's /effort/
if ((*list)->prev != NULL) {
printf("That is not the head of the Linked List!\n");
return;
}
int i = 0;
ll_int_t* curr = *list;
do {
printf("(%d): %d\n", i++, curr->value);
} while((curr = curr->next) != NULL); //sneaky
free(curr); // ????????????
}
有了这些,你需要一个void printLL(const ll_int_t *list)
{
if (list && list->prev) {
printf("That is not the head of the Linked List!\n");
return;
}
for (int i=0; list; list = list->next)
printf("(%d): %d\n", i++, list->value);
}
函数,给定一个正确构造列表的地址,将正确地销毁它。可以这样做的一种方法是:
freeList
当你真的要销毁整个列表时,这会派上用场。
接下来,void freeList(ll_int_t **head)
{
if ((*head) && (*head)->prev) {
printf("That is not the head of the Linked List!\n");
return;
}
while (*head)
{
void *p = *head;
*head = (*head)->next;
free(p);
}
}
vs delHead
的实施。你正在倒退他们。前者应该用后者实现,而不是相反。例如:
delElementAt
最后,bool delElementAt(ll_int_t** list, int pos)
{
bool res = false;
ll_int_t* prev = NULL;
while (*list && pos--)
{
prev = *list;
list = &(*list)->next;
}
if (*list)
{
void *p = *list;
*list = (*list)->next;
(*list)->prev = prev;
free(p);
res = true;
}
return res;
}
void delHead(ll_int_t** list)
{
delElementAt(list, 0);
}
函数似乎只是附加到列表中。如果您在自己的结构中包含链接列表,则有更好的方法来管理此操作,我将此作为练习留给您。无论如何,这是一个通过地址传递头指针是正确的地方,因为你可以正确地初始化一个空(并因此为空)列表:
addElement
将它放在一起
最终代码如下所示,包括退出时释放列表的附录。这应该通过valgrind无泄漏:
void addElement(ll_int_t **head, int value)
{
ll_int_t *prev = NULL;
while (*head)
{
prev = *head;
head = &(*head)->next;
}
*head = newNode(value);
(*head)->prev = prev;
}
输出(指针值明显不同)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//Doubly-linked lists brah
typedef struct ll_int {
struct ll_int* prev;
int value;
struct ll_int* next;
} ll_int_t;
ll_int_t* newNode(int value)
{
//Allocate the memory
ll_int_t* new = malloc(sizeof *new);
//If it was not successfully created, exit
if (new == NULL) {
fprintf(stderr, "Unable to allocate memory for new LL node\n");
exit(EXIT_FAILURE);
}
//Set up the node
new->value = value;
new->prev = NULL;
new->next = NULL;
return new;
}
void addElement(ll_int_t **head, int value)
{
ll_int_t *prev = NULL;
while (*head)
{
prev = *head;
head = &(*head)->next;
}
*head = newNode(value);
(*head)->prev = prev;
}
int lenList(const ll_int_t *head)
{
int i = 0;
for (; head; ++i, head = head->next);
return i;
}
bool delElementAt(ll_int_t** list, int pos)
{
bool res = false;
ll_int_t* prev = NULL;
while (*list && pos--)
{
prev = *list;
list = &(*list)->next;
}
if (*list)
{
void *p = *list;
*list = (*list)->next;
(*list)->prev = prev;
free(p);
res = true;
}
return res;
}
void delHead(ll_int_t** list)
{
delElementAt(list, 0);
}
void freeList(ll_int_t **head)
{
if ((*head) && (*head)->prev) {
printf("That is not the head of the Linked List!\n");
return;
}
while (*head)
{
void *p = *head;
*head = (*head)->next;
free(p);
}
}
void printLL(const ll_int_t* list)
{
if (list && list->prev) {
printf("That is not the head of the Linked List!\n");
return;
}
for (int i=0; list; list = list->next)
printf("(%d): %d\t%8p : %8p : %8p\n", i++, list->value, list->prev, list, list->next);
}
int main (int argc, char** argv)
{
ll_int_t* head = NULL;
addElement(&head, 5);
addElement(&head, 10);
printf("lenList: %d\n", lenList(head));
addElement(&head, 15);
printf("lenList: %d\n", lenList(head));
addElement(&head, 20);
printf("lenList: %d\n", lenList(head));
printLL(head);
delElementAt(&head, 1);
printLL(head);
delHead(&head);
printLL(head);
freeList(&head);
return(EXIT_SUCCESS);
}