堆栈弹出/推送细分错误,错误的相同数字

时间:2019-01-03 11:16:47

标签: c segmentation-fault return stack

我正在写一个堆栈,该堆栈是数据的链接列表(无效类型)。我正在测试的数据是

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: %lu\n", 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: %d\t", size1);
printf("size of data from fs1: %d\n", size2);

(2个堆栈是彼此的副本,因此我输入的内容应该与我阅读的相同)。当我在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,) 双重自由或腐败(fasttop) 中止(核心已弃用)

大小与输入的数据相同,但是值和名称不正确(即使在非复制堆栈中),应该是:

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(如推入的那个))时,它将导致核心错误。

如果我推送相同的数据并读取相同的数据(如返回节点所示,因为即使输出奇怪的输出也是如此),为什么当我返回原本应该打印的数据时却出现核心分割错误像上面的例子一样?

它看起来仅在我读取data2而不是data1时才发生。这是我用来读写文件的代码:

写:

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 stack\n");
                aux = my_stack_pop(stackRead);
                my_stack_push(InvertedStack, aux);
                }
                return InvertedStack;
        }
}

感谢任何有帮助的人。

该程序的MCVE,因此人们可以检查整个代码并提供更好的帮助:

test2.c:

https://codeshare.io/244eN4

my_lib.c: https://codeshare.io/G7L8Ab

my_lib.h:

https://codeshare.io/5DzZOm

有了这个,一旦对我发生的事情进行编译,您应该拥有更广阔的视野和一个可执行文件。

2 个答案:

答案 0 :(得分:2)

您在my_stack_pop中遇到问题

void *ret =  malloc(sizeof(struct my_stack_node));
ret = node->data;

malloc 是无用的(并造成内存泄漏),您也错过了释放 node 的机会

您可以将这两行替换为:

void * ret = node->data;

free(node);

其他备注

  • my_stack_push中检查错误之前进行分配,或者在出现错误的情况下释放 nodeToPush 否则会导致内存泄漏
  • sizeof(x),其中 x void*,如果您使用的是32b CPU,则始终为4;如果您使用的是64b CPU,则始终为8。例如, sizeof 不是 strlen

最后,关于两个堆栈是彼此的副本,所以我输入的内容应该相同很难帮助您,因为您没有说明如何克隆堆栈


(编辑后的备注)

my_stack_write

  • 不要使用分配初始化 aux ,这又会导致内存泄漏。
  • 转储my_stack_node的内存不起作用,您的目标是保存数据(包含my_data的数据),而不是指向数据的单元格

my_stack_read

  • my_stack_node使用动态分配是没有用的,可以将其放入堆栈(struct my_stack_node stackNode;)中,否则不要忘记释放它,因为您再次引入了内存泄漏。 li>
  • 读取my_stack_node时出现相同错误,您必须读取保存的数据(my_data
  • stackNode = (struct my_stack_node*) stackNode;什么也不做,因为它自己设置了 stackNode
  • my_stack_push(stackRead, stackNode->data);达不到预期的结果,因为stackNode->data在文件中读取了错误的值。

读写都错了,这就是为什么两个堆栈没有相同内容的原因

答案 1 :(得分:1)

设计良好,可靠且一致的API和库是一项艰巨的工作,尤其是在编程语言中,创建接口要比在面向对象的语言中难得多。

我的意思是非常好,但是您发布的代码存在内存泄漏,未处理的错误,未定义的行为和不良的设计。 sizeof运算符被滥用。我只能猜测您无法理解内存分配的工作原理以及指针和常规void*指针的概念。

好吧,我们走吧。

所以代码将出现段错误的原因是:

  1. 怀疑,堆栈指向的数据无效。确实,它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
}
  1. 您可以在while循环的main()中将空闲指针加倍。 s1fs1都是通过在同一文件上调用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
}
  • 修复错误后,代码将运行并打印“所有测试已通过”,可用的实时版本为here。无论如何,以下是一些注意事项:

    1. 无需在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));
    ...
  1. my_stack_read泄漏内存:

struct my_stack_node *aux = malloc(sizeof(struct my_stack_node));
...
aux = my_stack_pop(stackRead);
  1. 您发布的代码中的缩进有点偏离。尝试保留一种缩进样式,我可以宣传旧的'Linux kernel coding style,但您可以使用任何一致的东西。同样,您的代码也可以使用一些重组方法-限制变量的范围,或者像在else之后删除return NULL一样可以提高可读性。

  2. sizeof返回size_t。打印size_t的正确方法是使用"%zu" printf修饰符。您可以通过强制转换为void*并使用"%p" printf修饰符来打印指针。

  3. 通常,这确实不错,但是您需要了解指针指向数据,并且指针本身就是数据(因为它们具有价值)。当前,您的实现仅存储指向数据的指针,因此,客户端代码负责释放指针。在这种实现中容易陷入混乱。可以重写您的堆栈实现,以便为节点分配数据本身的内存,从而无需使用客户端代码以任何特殊方式处理内存。它可能看起来像:

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中使用参数进行初始化。

  1. 从序列化的角度来看,将指针值存储在文件中似乎是个坏主意。指针值在两次运行之间改变。存储指向列表元素的指针确实很糟糕。最好存储数据本身的内存,我认为没有理由存储列表的指针值。请注意,在当前实现中,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 }

{{1}}