我正在编写一个堆栈,它是数据的链接列表(void类型)。我正在测试的数据是
struct my_data {
int val;
char name[60];
};
struct my_stack_node {
void *data;
struct my_stack_node *next;
};
struct my_stack {
int size;
struct my_stack_node *first;
};
用于推送的数据初始化如下:
s1 = my_stack_init(sizeof(struct my_data));
if (!s1) {
puts("Error in my_stack_init()");
exit(1);
}
printf("ns1 initialized, size of data: %lun", sizeof(struct my_data));
for (int i = 0; i < NODES; i++) {
data = malloc(sizeof(struct my_data)); // We must allocate static memory
data->val = i;
sprintf(data->name, "Value %d", i);
if (my_stack_push(s1, data)) {
puts("Error in my_stack_push()");
exit(1);
}
} //s1 is the stack we are using here
并按my_stack_push(s2,data)推送它们;堆栈和数据作为参数。
我的推送功能是这样的:
int my_stack_push(struct my_stack *stack, void *data){
if(stack == NULL && sizeof(data)> 0){
printf("Null Stack or data size error.n");
//la pila debe existir
return -1;
}
else {
struct my_stack_node *nodeToPush = malloc(sizeof(struct my_stack_node));
nodeToPush -> data = data;
if(stack -> first == NULL) {
nodeToPush -> next = NULL;
stack -> first = nodeToPush;
}
else {
nodeToPush -> next = stack -> first;
stack -> first = nodeToPush;
}
}
return 0;
}
我的弹出功能是这个
void *my_stack_pop(struct my_stack *stack){
struct my_stack_node *node = stack->first;
if(stack->first == NULL){
return 0;
}
stack->first = node->next;
void *ret = node->data;
free(node);
return ret;
}
但总的来说,当我弹出它们并尝试比较它们时,我得到了一个分段错误:
while ((data1 = my_stack_pop(s1))) {
data2 = my_stack_pop(fs1);
printf("Node of s1: (%d, %s)t", data1->val, data1->name);
printf("Node of fs1: (%d, %s)n", data2->val, data2->name);
if (!data2 || data1->val != data2->val || my_strcmp(data1->name, data2->name)) {
printf("Data in s1 and fs1 are not the same.n (data1->val: %d <> data2->val: %d) o (data1->name: %s <> data2->name: "
"%s)n",
data1->val, data2->val, data1->name, data2->name);
exit(1);
}
size1 = sizeof(*data1);
size2 = sizeof(*data2);
free(data1);
free(data2);
}
printf("size of data from s1: %dt", size1);
printf("size of data from fs1: %dn", size2);
(这两个堆栈是彼此的副本,所以我输入的内容应该与我阅读的内容相同)。当我在 pop 函数中返回整个节点(不是数据,而是整个my_stack_node)时,一切都正确。但错了:
Comparing the data...
s1 的节点:(0,值 0)//好一个 fs1 的节点:(0,值 0) 8 8
s1 的节点:(-1203217792,NV)//这里开始出错 fs1 的节点:(-1203217792,NV) 8 8
s1 的节点:(-1203217792、NV) fs1 的节点:(-1203217792,NV) 8 8
s1 的节点:(-1203217792、NV) fs1 的节点:(-1203217792,NV) 8 8
s1 的节点:(0, ) fs1 的节点: (0, ) 双重释放或损坏(快速顶部) 已中止(核心已转储)
大小与输入的数据相同,但值和名称错误(即使在非复制堆栈中),应该是:
New node in s1: (0, Value 0)
New node in s1: (1, Value 1)
New node in s1: (2, Value 2)
New node in s1: (3, Value 3)
New node in s1: (4, Value 4)
New node in s1: (5, Value 5)
New node in s1: (6, Value 6)
New node in s1: (7, Value 7)
New node in s1: (8, Value 8)
New node in s1: (9, Value 9)
但是当我像代码一样返回(在我的堆栈弹出上)数据本身时,我被核心转储在测试的打印中。(长度为 8 个字节的数据,如一个输入)。
当我返回节点(大小 = 64)时,它正确打印了错误的数据,但是当我返回数据(大小 = 8(就像推送的那个))时,它的核心错误。
如果我推送相同的数据并读取相同的数据(如返回节点时所示,因为即使奇怪的输出也是相同的),为什么当我返回应该像上面的例子一样打印的数据时会出现核心分段错误?
看起来它只发生在我读取数据2而不是数据1时。这是我用来编写和读取文件的代码:
写:
int my_stack_write(struct my_stack *stack, char *filename){
int count = 0;
struct my_stack_node *aux =malloc(sizeof(struct my_stack_node));
FILE *file = fopen(filename, "wb");
if(stack->first != NULL){
aux = stack->first;
count++;
while(aux->next != NULL){
printf("New node in s1: (%p)n", aux->data);
fwrite(aux ,sizeof(struct my_stack_node), 1, file);
aux = aux->next;
count++;
}
printf("New node in s1: (%p)n", aux->data);
fwrite(aux ,sizeof(struct my_stack_node), 1, file);
}
fclose(file);
return count;
}
读:
struct my_stack *my_stack_read(char *filename){
struct my_stack *stackRead = my_stack_init(sizeof(struct my_stack_node));
struct my_stack_node *stackNode = malloc(sizeof(struct my_stack_node));
FILE *file = fopen(filename, "rb");
if(!file){
puts("Impossible obrir el fitxer");
return NULL;
}else{
while(fread(stackNode, sizeof(struct my_stack_node), 1, file)){
printf("New node in fs1: (%p)n", stackNode->data);
stackNode = (struct my_stack_node*) stackNode;
my_stack_push(stackRead, stackNode->data);
}
fclose(file);
struct my_stack *InvertedStack = my_stack_init(sizeof(struct my_stack_node));
struct my_stack_node *aux = malloc(sizeof(struct my_stack_node));
while(my_stack_len(stackRead) !=0){
printf("Inverting the stackn");
aux = my_stack_pop(stackRead);
my_stack_push(InvertedStack, aux);
}
return InvertedStack;
}
}
感谢任何提供帮助的人。
MCVE的程序,因此人们可以检查整个代码并更好地提供帮助:
测试2.c:
https://codeshare.io/244eN4
my_lib.c: https://codeshare.io/G7L8Ab
my_lib.h:
https://codeshare.io/5DzZOm
有了这个,你应该有一个更广阔的视野和一个可执行文件,一旦编译到我身上发生的事情。
您在my_stack_pop
中遇到问题
void *ret = malloc(sizeof(struct my_stack_node));
ret = node->data;
malloc是无用的(并产生内存泄漏),你也错过了释放节点
您可以将这 2 行替换为:
void * ret = node->data;
free(node);
其他备注
- 在
my_stack_push
之前检查错误以进行分配,或者在发生错误时释放nodeToPush,否则您有内存泄漏 sizeof(x)
x是void*
如果您使用的是 32b CPU,则始终值为 4,如果是 64b CPU,则始终值为 8。例如,sizeof不是strlen
最后关于2 个堆栈是彼此的副本,所以我输入的内容应该是一样的,很难帮助您,因为您没有说您是如何克隆堆栈的
(编辑后备注)
在my_stack_write
- 不要使用分配初始化AUX,这样做会再次出现内存泄漏。
- 要转储
my_stack_node
不起作用的内存,您的目标是保存数据(包含my_data
),而不是指向数据的单元格
在my_stack_read
- 对
my_stack_node
使用动态分配是没有用的,可以将其放入堆栈(struct my_stack_node stackNode;
),否则不要忘记释放它,因为您再次引入内存泄漏。 - 读取
my_stack_node
时相同的错误,您必须读取保存的数据(my_data
) stackNode = (struct my_stack_node*) stackNode;
什么都不做,因为它设置了堆栈节点my_stack_push(stackRead, stackNode->data);
没有预期的结果,因为stackNode->data
文件中读取了错误的值。
读写都是错误的,这就是为什么两个堆栈的内容不一样的原因
设计良好、可靠和一致的 API 和库是一项非常艰巨的工作,尤其是在编程语言中,创建接口比在面向对象语言中要困难一些。
我的意思是真的很好,但是您发布的代码存在内存泄漏,未处理的错误,未定义的行为和糟糕的设计。运算符的大小被误用。我只能猜测你不明白内存分配是如何工作的,以及指针和一般void*
指针的概念。
好吧,我们走吧。
因此,代码会出错的原因是:
- 正如所怀疑的那样,堆栈指向的数据无效。事实上,它被
malloc
了,但随后又free
了几行:
for (int i = 0; i < NODES; i++) {
struct ... * data = malloc(sizeof(struct my_data));
my_stack_push(s1, data); // pushes the data pointer to the stack
void *data1 = my_stack_pop(s1); // pops the data pointer to the stack
...
assert(data1 == data); // data and data1 are the same
free(data1); // and data get's freed
// the memory behind both data and data1 is freed in this point
// thus the pointer s1.first->node->data is invalid
// as the code runs in loop, effectively all the data in this stack are invalid
}
- 在 while 循环中
main()
中双倍自由指针。s1
和fs1
都是通过调用同一文件上的my_stack_read
获得的 - 因此从逻辑上讲,它们应该包含相同的值。由于它们存储相同的值,因此存储的数据指针是相同的,因此释放指针也将释放第二个列表中的第二个指针并使其无效。双重释放是未定义的行为,我想应该会导致类似于正常系统上的分段错误。
while ((data1 = my_stack_pop(s1))) {
data2 = my_stack_pop(fs1);
...
assert(data1 == data2); // same pointers
free(data1);
free(data2); // double free corruption - seg fault
}
修复错误后,代码将运行并打印"所有测试均通过",实时版本可在此处获得。无论如何,以下是一些注意事项:
- 不需要
- 在
my_stack_init
中分配堆栈数组:
- 在
struct my_stack *my_stack_init(int size){
struct my_stack *stack = malloc(sizeof(struct my_stack_node) * size);
...
stack
现在指向size
内存的计数sizeof(struct my_stack_node)
字节。您只需要一个my_stack_node结构。此外,sizeof
返回size_t
。更好的版本是:
struct my_stack *my_stack_init(size_t size){
struct my_stack *stack = malloc(sizeof(struct my_stack_node));
...
my_stack_read
泄漏内存:
struct my_stack_node *aux = malloc(sizeof(struct my_stack_node));
...
aux = my_stack_pop(stackRead);
您发布的代码中的缩进有点不对劲。 尽量保持一种缩进样式,我可以宣传旧的 Linux 内核编码风格,但您可以使用任何东西,但要保持一致。此外,您的代码可以使用一些重组 - 限制变量的范围或像在
return NULL
后删除else
可能会增加可读性。sizeof
返回size_t
.打印size_t
的正确方法是使用"%zu"
printf 修饰符。可以通过强制转换为void*
并使用 printf 修饰符来打印指针"%p"
。一般来说,这确实是很好的工作,但你需要明白指针指向数据并且本身就是数据(因为它们有价值)。目前,您的实现仅存储指向数据的指针,因此客户端代码负责释放指针。在这样的实现中,很容易让自己陷入混乱。可以重写堆栈实现,为节点和数据本身分配内存,从而释放客户端代码以任何特殊方式处理内存的需要。它可能看起来像:
int my_stack_push(struct my_stack *stack, void *data) {
...
// allocate memory for new link in the list
struct my_stack_node *nodeToPush = malloc(sizeof(struct my_stack_node));
if (nodeToPush == NULL) abort();
// allocate memory for data itself
nodeToPush->data = malloc(stack->size);
if (nodeToPush->data == NULL) abort();
// memory copy the data into the pointer
memcpy(nodeToPush->data, data, stack->size);
..
在这样的实现中,堆栈负责释放指针并仅存储数据的副本。因此,需要重写所有句柄以支持这一点。数据的大小可通过stack->size
获得,并使用参数my_stack_init
初始化。
- 从序列化的角度来看,在文件中存储指针值似乎是一个坏主意。指针值在运行之间更改。存储指向列表元素的指针感觉非常糟糕。最好存储数据本身的内存,我认为没有理由存储列表的指针值。请注意,在当前的实现中,
stackNode->next
的值确实没有在my_stack_read
中使用,因为它的值之前已经被释放了。 我不明白如果你从不使用它,为什么你甚至将stackNode->next
的值写入文件。
因此,我们可以将数据本身存储在文件中:
int my_stack_write(struct my_stack *stack, char *filename){
...
node = stack->first;
void *data = node->data;
// write the data behind the node to the file
if (fwrite(data, stack->size, 1, file) != 1) {
return -100;
}
}
以类似的方式,我们可以重写my_stack_read
:
struct my_stack *my_stack_read(char *filename, size_t size) {
...
struct my_stack *stack = stack_init(size);
...
void *newdata = malloc(stack->size);
while (fread(newdata, stack->size, 1, file)) {
my_stack_push(stack, newdata);
}
free(newdata); // as in my proposed implementation my_stack stores copy
// of the memory behind the pointers, we can safely manage own memory
// by freeing the pointer
}