在循环内将堆栈变量分配给malloc的内存会更改链表中的数据

时间:2019-01-11 20:38:35

标签: c linked-list malloc

因此,我具有此功能,可以动态分配一个缓冲区,该缓冲区的大小足以容纳文本文件(fgetLine)中任意长度的字符串。我在循环中使用此功能来逐行处理文本文件。 我想将文本文件中每行的不同字段存储在循环链接列表内的文本文件中,但是,似乎动态分配函数返回的行不断被覆盖,因此仅文件的最后一项存储在内部链表。我该如何解决?

我已经使用gdb进行了研究,我的循环链表实现工作正常,但是我不明白为什么更新变量line会不断更改存储在堆栈结构scale中的值,即使移至链表中的其他节点后,也会循环前一次迭代。也就是说,存储在先前节点中的scale.name会根据当前循环迭代以及已分配给line的内容进行更改。我以为也许我应该在迭代之间释放line,但这只会阻止任何内容存储在节点中。请帮忙!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "List.h"

#define DATA(L) ((L)->data)
#define NEXT(L) ((L)->next)
#define BACK(L) ((L)->back)

typedef struct node_t {
    void          *data;
    struct node_t *back;
    struct node_t *next;
} node_t;

char*
fgetLine(FILE *stream);

struct scale_t {
    char *name;
    char *intervals;
};

int
main(int argc,
     char *argv[])
{
    FILE *fp = fopen(argv[1], "r");

    node_t *head = List_createnode(NULL);

    /*** TROUBLE AREA ***/
    for (char *line; (line = fgetLine(fp));) {
        struct scale_t scale;
        scale.name = strtok(line, ",\t");
        scale.intervals = strtok(NULL, ",\040\t");
        List_prepend(head, &scale);
    }

    node_t *cur = NEXT(head);
    while (DATA(cur)) {
        puts((*((struct scale_t *)DATA(cur))).name);
        cur = NEXT(cur);
    }
}

char*
fgetLine(FILE *stream)
{
    const size_t chunk = 128;
    size_t max = chunk;

    /* Preliminary check */
    if (!stream || feof(stream))
        return NULL;

    char *buffer = (char *)malloc(chunk * sizeof(char));
    if (!buffer) {
        perror("Unable to allocate space");
        return NULL;
    }
    char *ptr = buffer;
    for (; (*ptr = fgetc(stream)) != EOF && *ptr != '\n'; ++ptr) {

        size_t offset = ptr - buffer;
        if (offset >= max) {
            max += chunk;

            char *tmp = realloc(buffer, max);
            if (!tmp) {
                free(buffer);
                return NULL;
            }
            buffer = tmp;
            ptr = tmp + offset;
        }
    }
    *ptr = '\0';
    return buffer;
}


/* in List.h */
typedef enum { OK,    ERROR } status_t;
typedef enum { FALSE, TRUE  } bool;

node_t*
List_createnode(void *Data)
{
    node_t *node_new = (node_t *)malloc(sizeof(node_t));
    if (!node_new) {
        perror("Unable to allocate node_t.");
        return NULL;
    }
    DATA(node_new) = Data;

    /* Leave this assignment to other functions. */
    NEXT(node_new) = NULL;
    BACK(node_new) = NULL;

    return node_new;
}

status_t
List_prepend(node_t *next,
             void   *data)
{
    if (!next)
        return ERROR;

    node_t *node_new = List_createnode(data);
    if (!node_new) {
        perror("Unable to allocate node_t.");
        return ERROR;
    }
    DATA(node_new) = data;
    NEXT(node_new) = next;

    /* If BACK(next) is NULL then 'next' node_t is the only node in the list. */
    if (!BACK(next)) {
        BACK(node_new) = next;
        NEXT(next) = node_new;
    } else {
        /* When BACK(next) is not NULL store this into BACK(node_new).. */
        BACK(node_new) = BACK(next);

        /* Prepending to 'next' node is same as appending to the node originally
         * pointed to by BACK(next). */
        NEXT(BACK(next)) = node_new;
    }
    /* Now update BACK(next) to point to the new prepended node. */
    BACK(next) = node_new;
    return OK;
}

2 个答案:

答案 0 :(得分:2)

这是我的主要评论开头。

现在已经发布了足够的代码...

关键问题在于,main中的scale loop 范围内的(即分配了 not 堆)

因此,即使fgetLine返回一个malloc的缓冲区,并且strtok调用的结果指向该缓冲区内的 ,地址{{1 }}传递给scale将是List_prepend中每次迭代的相同地址。

main的{​​{1}}参数不是{em>不是 List_prependmalloc(并且不知道它需要使用的长度),因此memcpy呼叫者必须执行此操作。

因此,我们必须在data中通过更改以下内容来解决此问题:

List_prepend

进入:

main

更新:

  

是否有一种现象的名称,其中“传递给List_prepend的标度的地址在main的每次迭代中将是相同的地址”?我以为处于循环作用域将意味着每次都会创建一个新的刻度,并且我可以将这些临时值转移到List_prepend上。

循环作用域和函数作用域变量最终出现在函数堆栈框架中。如果将for (char *line; (line = fgetLine(fp));) { struct scale_t scale; scale.name = strtok(line, ",\t"); scale.intervals = strtok(NULL, ",\040\t"); List_prepend(head, &scale); } 移至函数作用域,可能会更容易理解为什么不起作用

循环作用域可能会对堆栈指针进行一些调整(或者可能会)。它可以可以编译代码,就像定义是函数作用域一样。

或者,它可以这样做:

在循环顶部,将堆栈指针减for (char *line; (line = fgetLine(fp));) { struct scale_t *scale = malloc(sizeof(struct scale_t)); scale->name = strtok(line, ",\t"); scale->intervals = strtok(NULL, ",\040\t"); List_prepend(head, scale); } [并适当对齐]。

然后,struct scale_t scale;获得该地址。这将传递给sizeof(struct stack_t)

在循环的底部,scale将“超出范围”,因此堆栈指针将增加List_prepend

现在,堆栈指针再次具有其原始值。它在上一个循环迭代的顶部有一个。

起泡,冲洗,重复...

优化的编译器可以看到在循环内 进行减量/增量序列是浪费的。它可以将递减量 移到循环上方,然后将增量移到循环后,达到相同的效果。

答案 1 :(得分:1)

在缓冲区分配已满时,您正在写超出缓冲区末尾的内容,因为在进行大小检查之前已向其写入数据。我建议像这样使用while循环:

char *ptr = buffer;
int ch;                                 // int not char
while((ch = fgetc(stream)) != EOF && ch != '\n') {

    size_t offset = ptr - buffer;
    if (offset >= max - 1) {            // allow room for terminator
        max += chunk;

        char *tmp = realloc(buffer, max);
        if (!tmp) {
            free(buffer);
            return NULL;
        }
        buffer = tmp;
        ptr = tmp + offset;
    }
    *ptr++ = ch;                       // now write to buffer
}
*ptr = '\0';