为什么C中的通用链表中存储的数据已损坏

Why is stored data corrupted in a generic linked list in C?

本文关键字:存储 数据 已损坏 链表 为什么      更新时间:2023-10-16

我在C中创建了一个通用链表来存储任何类型的数据。当然,这涉及到使用void指针。我知道代码正常工作,但我的问题可能是由于存储字符数组(即C字符串(的void指针周围的内存管理。可能是因为我对C中的void指针缺乏深入的理解?

出于测试目的,最好使用下面的代码示例来展示我的问题。

slist.h头文件

#ifndef _SLIST_H
#define _SLIST_H
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Linked list node data structure for the client program */
typedef struct _listNode {
void *datum;
struct _listNode *next;
} listNode, *plistNode;    
/* Generic pointer to data iterators */
typedef void(*listIterator)(void *);
typedef struct {
unsigned int length;
unsigned int storageSize;
listNode *head;
listNode *tail;
} Slist;
/* Prototype functions */
Slist *createList(const int datasize);
unsigned int destroyList(Slist *pHead);
listNode *insert(Slist *pHead, const void *data);
void iterate(Slist *pHead, listIterator iterator);
#ifdef __cplusplus
}
#endif
#endif

链表源代码-slist.cpp

#include "slist.h"
Slist *createList(const int datasize) {
Slist *pHead;
pHead = (Slist *)malloc(sizeof(Slist));
if (pHead) {
pHead->length = 0;
pHead->storageSize = datasize;
pHead->head = NULL;
pHead->tail = NULL;
}
return pHead;
}
unsigned int destroyList(Slist *pHead) {
unsigned int count = 0;
listNode *tnode;
listNode *cnode = pHead->head;
while (cnode) {
count++;
tnode = cnode;
cnode = cnode->next;
free(tnode->datum);
free(tnode);
}
free(pHead);
pHead = NULL;
return count;
}
listNode *insert(Slist *pHead, const void *data) {
listNode *node = (listNode *)malloc(sizeof(listNode));
if (node) {
node->datum = malloc(pHead->storageSize);
if (node->datum) {
memcpy(node->datum, data, pHead->storageSize);
node->next = NULL;
if (pHead->length > 0) {
pHead->tail->next = node;
pHead->tail = node;
} else {
pHead->head = node;
pHead->tail = node;
}
pHead->length++;
} else {
free(node);
node = NULL;
}
}
return node;
}
void iterate(Slist *pHead, listIterator iterator) {
listNode *node = pHead->head;
while (node) {
iterator(node->datum);
node = node->next;
}
}

主程序

#include "slist.h"
void iterate_string(void *data) {
printf("Address: %p. Data: %s (Ascii char value)n", data, *(char**)data);
}
void testStringList() {
char *buf = NULL;
Slist *list = createList(sizeof(char *));
const char *names[] = { "Toyota", "Mercdes", "Jaguar", "Lotus", "Hyundai", "Volkswagen" };

for (int i = 0; i < (sizeof(names) / sizeof(names[0])); i++) {
int len = strlen(names[i]) + 1;
buf = (char *)malloc(len * sizeof(char));
strcpy_s(buf, len, names[i]);
listNode *ptr = insert(list, &buf);
printf("TEST: Data received after insert -> %s at address %pn", *(char**)ptr->datum, ptr->datum);
free(buf);  <-- NOTE: Is that the issue???? Did I misuse it?
printf("TEST: Data after releasing temp buffer -> %s at address %pnn", *(char**)ptr->datum, ptr->datum);
}
iterate(list, iterate_string);
int killCount = destroyList(list);
printf("nList destroyed after killing %d strings...n", killCount);
}
int main(int argc, char **argv) {
testStringList();
return 0;
}

生成的输出1

TEST: Data received after insert -> Toyota at address 005642F0
TEST: Data after releasing temp buffer -> ннннннннннн at address 005642F0
TEST: Data received after insert -> Mercdes at address 00567A50
TEST: Data after releasing temp buffer -> ннннннннннннu at address 00567A50
TEST: Data received after insert -> Jaguar at address 00567AB8
TEST: Data after releasing temp buffer -> ннннннннннннu at address 00567AB8
TEST: Data received after insert -> Lotus at address 00567B20
TEST: Data after releasing temp buffer -> ннннннннннннu at address 00567B20
TEST: Data received after insert -> Hyundai at address 00567B88
TEST: Data after releasing temp buffer -> ннннннннннннu at address 00567B88
TEST: Data received after insert -> Volkswagen at address 00567BF0
TEST: Data after releasing temp buffer -> ннннннннннннннн at address 00567BF0
Address: 005642F0. Data: ннннннннннннннн (Ascii char value)
Address: 00567A50. Data: ннннннннннннннн (Ascii char value)
Address: 00567AB8. Data: ннннннннннннннн (Ascii char value)
Address: 00567B20. Data: ннннннннннннннн (Ascii char value)
Address: 00567B88. Data: ннннннннннннннн (Ascii char value)
Address: 00567BF0. Data: ннннннннннннннн (Ascii char value)
List destroyed after killing 6 strings...

生成的输出2

TEST: Data received after insert -> Toyota at address 007F3DB0
TEST: Data after releasing temp buffer -> Toyota at address 007F3DB0
TEST: Data received after insert -> Mercdes at address 007F3E18
TEST: Data after releasing temp buffer -> Mercdes at address 007F3E18
TEST: Data received after insert -> Jaguar at address 007F78D8
TEST: Data after releasing temp buffer -> Jaguar at address 007F78D8
TEST: Data received after insert -> Lotus at address 007F7978
TEST: Data after releasing temp buffer -> Lotus at address 007F7978
TEST: Data received after insert -> Hyundai at address 007F7A18
TEST: Data after releasing temp buffer -> Hyundai at address 007F7A18
TEST: Data received after insert -> Volkswagen at address 007F04D0
TEST: Data after releasing temp buffer -> Volkswagen at address 007F04D0
Address: 007F3DB0. Data: Toyota (Ascii char value)
Address: 007F3E18. Data: Mercdes (Ascii char value)
Address: 007F78D8. Data: Jaguar (Ascii char value)
Address: 007F7978. Data: Lotus (Ascii char value)
Address: 007F7A18. Data: Hyundai (Ascii char value)
Address: 007F04D0. Data: Volkswagen (Ascii char value)
List destroyed after killing 6 strings...

正如您所看到的,我想要字符串的动态存储,并通过void指针存储在通用链表中。为了进行测试,buf变量只是从硬编码数组中动态复制字符串,然后存储在列表中。一旦复制了每个数据,buf就会被释放,然后重新分配给下一个数据,以此类推

但结果并不是我所希望的(见输出1(。我检查了指针的地址,以确保我在内存中找到了正确的位置,但结果已损坏或丢失。

但是,当我注释掉free(buf)时,结果是令人满意的(参见输出2(,尽管内存明显泄漏。

有人能发现我的代码出了什么问题并告诉我吗?我确信我一定忽略了什么?非常感谢。

主要是:

listNode* ptr = insert(list, &buf);

你希望它是:

listNode* ptr = insert(list, buf);

因为您要存储buf指向的数据,而不是buf本身,它将随着每次迭代或调用而更改。

事实上,你没有;我不想free(buf),因为内存正在使用中(您刚刚将其存储在列表中(。

您的代码不是c++代码,您应该使用c编译器,而不是c++编译器。

通用列表容器是为处理固定大小的对象而设计的。在您的示例中,这些对象是char *指针。您可以将指针直接传递给insert函数,代码将按预期工作。以下是testStringList:的修改版本

void testStringList() {
Slist *list = createList(sizeof(char *));
const char *names[] = { "Toyota", "Mercedes", "Jaguar", "Lotus", "Hyundai", "Volkswagen" };

for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
insert(list, &names[i]);
}
iterate(list, iterate_string);
int killCount = destroyList(list);
printf("nList destroyed after killing %d strings...n", killCount);
}

只有当字符串是恒定的,或者至少当它们的寿命比列表的寿命长,并且您可以单独跟踪它们的分配时,这才有效。

如果希望容器处理可变大小的数据,其中每个列表节点的数据大小都可以更改,并制作数据副本,则必须更改API和实现。

以下是一个适当列表类型的修改实现:

vlist.h头文件


#ifndef VLIST_H__
#define VLIST_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Linked list node data structure for the client program */
typedef struct VlistNode {
void *datum;
size_t size;
struct VlistNode *next;
} VlistNode;
/* Generic pointer to vdata iterators */
typedef void (*VlistIterator)(void *datum, size_t size);
typedef struct {
size_t length;
VlistNode *head;
VlistNode *tail;
} Vlist;
/* Prototype functions */
Vlist *Vlist_create(void);
size_t Vlist_destroy(Vlist *pHead);
VlistNode *Vlist_insert(Vlist *pHead, const void *data, size_t size);
void Vlist_iterate(Vlist *pHead, VlistIterator iterator);
#ifdef __cplusplus
}
#endif
#endif // VLIST_H__

链表源代码-Vlist.c


#include "Vlist.h"
Vlist *Vlist_create(void) {
Vlist *pHead = (Vlist *)malloc(sizeof(Vlist));
if (pHead) {
pHead->length = 0;
pHead->head = NULL;
pHead->tail = NULL;
}
return pHead;
}
size_t Vlist_destroy(Vlist *pHead) {
size_t count = 0;
VlistNode *cnode = pHead->head;
while (cnode) {
VlistNode *tnode = cnode;
cnode = cnode->next;
free(tnode->datum);
free(tnode);
count++;
}
free(pHead);
return count;
}
VlistNode *Vlist_insert(Vlist *pHead, const void *data, size_t size) {
VlistNode *node = (VlistNode *)malloc(sizeof(VlistNode));
if (node) {
node->datum = malloc(size);
if (node->datum) {
memcpy(node->datum, data, size);
node->size = size;
node->next = NULL;
if (pHead->length > 0) {
pHead->tail->next = node;
pHead->tail = node;
} else {
pHead->head = node;
pHead->tail = node;
}
pHead->length++;
} else {
free(node);
node = NULL;
}
}
return node;
}
void Vlist_iterate(Vlist *pHead, VlistIterator iterator) {
VlistNode *node = pHead->head;
while (node) {
iterator(node->datum, node->size);
node = node->next;
}
}

主程序


#include "Vlist.h"
void iterate_string(void *data, size_t size) {
printf("Address: %p, Size: %u, Data: %s (Ascii string)n", data, (unsigned)size, data);
}
void testStringList() {
Vlist *list = Vlist_create();
const char *names[] = { "Toyota", "Mercedes", "Jaguar", "Lotus", "Hyundai", "Volkswagen" };
for (size_t i = 0; i < (sizeof(names) / sizeof(names[0])); i++) {
Vlist_insert(list, names[i], strlen(names[i]) + 1);
}
Vlist_iterate(list, iterate_string);
int killCount = Vlist_destroy(list);
printf("nList destroyed after killing %d strings...n", killCount);
}
int main(int argc, char **argv) {
testStringList();
return 0;
}

输出:

地址:0x7fe59d4026c0,尺寸:7,数据:丰田(Ascii字符串(地址:0x7fe59d4026f0,尺寸:9,数据:Mercedes(Ascii字符串(地址:0x7fe59d402720,大小:7,数据:Jaguar(Ascii字符串(地址:0x7fe59d402750,大小:6,数据:Lotus(Ascii字符串(地址:0x7fe59d402780,尺寸:8,数据:现代(Ascii字符串(地址:0x7fe59d4027b0,尺寸:11,数据:大众(Ascii字符串(杀死6个字符串后销毁的列表

在C++项目中编写C代码会使代码比实际情况复杂得多。您可以使用C++元素来解决这些问题。这是一个等价的C++代码:

#include <array>
#include <cstdio>
#include <list>
#include <string>
void iterate_string(const char *data)
{
std::printf("Address: %p. Data: %s (Ascii char value)n", data, data);
}
void testStringList() {
std::list<std::string> list;
const std::array<std::string, 6> names = { "Toyota", "Mercdes", "Jaguar", "Lotus", "Hyundai", "Volkswagen" };

for (const auto &name : names) {
list.push_back(name);
std::printf("TEST: Data received after insert -> %s at address %pn", list.back().c_str(), list.back().c_str());
// std::printf("TEST: Data after releasing temp buffer -> %s at address %pnn", list.back().c_str(), list.back().c_str());
}
for (const auto &name : list) {
iterate_string(name.c_str());
}
std::printf("nList destroyed after killing %zu strings...n", list.size());
}
int main()
{
testStringList();
return 0;
}

正如您所看到的,您不必处理内存泄漏。STL容器将为您做到这一点。