首页 > 解决方案 > 将堆栈变量分配给循环内的 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;
}

标签: clinked-listmalloc

解决方案


这是我的热门评论的开头。

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

关键问题是 inmain循环范围的(即scale分配堆)

因此,即使fgetLine返回一个malloced 缓冲区,并且strtok调用的结果指向缓冲区scale,传递到List_prepend的地址在.main

List_prepend不和它的参数(并且 知道它需要使用的长度),所以调用者必须这样做。mallocmemcpydataList_prepend

因此,我们必须main通过更改来解决这个问题:

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);
}

更新:

是否存在“传递给 List_prepend 的比例地址在 main 中的每次迭代中将是相同的地址”的现象的名称?我原以为在循环范围内意味着每次都会创建一个新的比例,我可以将这些临时值转移到 List_prepend 上。

循环范围和函数范围变量最终在函数堆栈框架中。如果您移至函数范围,可能会更容易理解为什么它不起作用。struct scale_t scale;

循环作用域可能会对堆栈指针进行一些处理[或者可能不会]。它可以只编译代码,就好像定义是函数范围的一样。

或者,它可能会这样做:

在循环顶部,堆栈指针递减sizeof(struct stack_t)[with proper alignment]。

然后,scale得到那个地址。这被传递给List_prepend.

在循环的底部,scale将“超出范围”,因此堆栈指针增加sizeof(struct stack_t).

现在,堆栈指针又恢复了原来的值。它在上一个循环迭代顶部的那个。

起泡、冲洗、重复……

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


推荐阅读