Присвоение переменной стека памяти malloc'd внутри цикла изменяет данные в связанном списке - PullRequest
0 голосов
/ 11 января 2019

Итак, у меня есть эта функция, которая динамически выделяет буфер, достаточно большой, чтобы вместить строку любой длины из текстового файла (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 голосов
/ 12 января 2019

Это предварено моими главными комментариями.

Теперь, когда достаточно кода было отправлено ...

Ключевая проблема заключается в том, что в main, scale - это цикл в области (т. Е. не выделена куча)

Таким образом, даже если fgetLine возвращает malloc ed буфер и результаты strtok точки вызова в пределах этого буфера, адрес scale, переданный List_prepend, будет один и тот же адрес на каждой итерации в main.

List_prepend делает не malloc и memcpy свой аргумент data (и не знает, какую длину ему нужно будет использовать), поэтому абонент из List_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);
}

UPDATE:

есть ли название для явления, в котором «адрес масштаба, передаваемый в List_prepend, будет тем же адресом на каждой итерации в main.»? Я думал, что нахождение в цикле будет означать, что каждый раз будет создаваться новый масштаб, и я мог бы перенести эти временные значения в List_prepend.

Переменные области видимости цикла и области действия попадают в кадр стека функций. Возможно, будет проще понять, почему не работает , если вы переместили struct scale_t scale; в область действия функции.

Loop scope может сделать небольшой обман с указателем стека [или может не ]. Он может просто скомпилировать код, как если бы определение было функциональной областью.

Или это может быть сделано:

В верхней части цикла указатель стека уменьшается на sizeof(struct stack_t) [с соответствующим выравниванием].

Затем scale получает этот адрес. Это передается List_prepend.

В нижней части цикла scale выйдет из области видимости, поэтому указатель стека увеличивается на sizeof(struct stack_t).

Теперь указатель стека снова имеет свое первоначальное значение. Тот, который был в верхней части предыдущей итерации цикла.

пена, промыть, повторить ...

Оптимизирующий компилятор может увидеть, что выполнение последовательности декремента / приращения внутри цикла бесполезно. Он может переместить декремент выше цикла и приращение после цикла, достигая того же эффекта.

0 голосов
/ 12 января 2019

Вы пишете после окончания выделения буфера, когда он заполнен, потому что вы записываете в него данные перед проверкой размера. Я предлагаю использовать цикл 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';
...