Управление памятью в связанных списках C - PullRequest
1 голос
/ 23 апреля 2020

Я пытаюсь реализовать структуру данных связанного списка, которая представляет дерево папок.

Структуры ниже:

typedef struct SRC_ERROR SRC_ERROR;
struct SRC_ERROR {
    int error_code;
    char *error;
};

typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
    char *entry;
    char md5[MD5_DIGEST_LENGTH];
};

typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
    SRC_ERROR error;
    char *name;
    char *full_path;
    SRC_FILE **entries;
    SRC *next_dir;
};

Идея заключалась в том, что каждый каталог будет храниться в * 1006. * SRC_FILE должен использоваться в качестве массива для хранения имени файла и MD5 га sh для каждого файла.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1013 У меня проблемы с управлением памятью. Я получаю периодические ошибки seg, когда каталог структурирован определенным образом.

Я отметил строку, что отладчик пометил

source->entries[count]->entry = entry->d_name; //SEGFAULT HERE 

Я думал, что выделил память для каждой структуры, но, возможно, я сделал это неправильно, или есть основная проблема с полностью структура данных?

Например:

test> tree
.
└── Text

0 directories, 1 file

Это вызывает ошибку сегмента. Принимая во внимание, что это не:

/test> tree
.
├── another sample
│   └── Text
└── sample folder

2 directories, 1 file

Дополнительные используемые функции:

SRC *add_dir(SRC *file_tree, SRC *new_dir) {
    new_dir->next_dir = file_tree;
    return new_dir;
}

SRC *insert_dir_node(char *name, char *full_path) {
    SRC *next_dir;
    next_dir = (SRC *)emalloc(sizeof(SRC));
    next_dir->name = name;
    next_dir->full_path = full_path;
    next_dir->next_dir = NULL;
    return next_dir;
}

Ответы [ 2 ]

0 голосов
/ 24 апреля 2020

Тема: Управление памятью в связанных списках . Действительно, это главная проблема в программе C, потому что нет автоматического управления памятью c. Вы должны решить и указать, как каждый объект, на который указывает указатель в ваших структурах, обрабатывается с точки зрения управления памятью. Является ли указатель ссылкой времени жизни объекта или время жизни обрабатывается где-то еще, а указатель является просто точкой доступа.

Давайте проанализируем определения вашего объекта:

typedef struct SRC_ERROR SRC_ERROR;
struct SRC_ERROR {
    int error_code;
    char *error;
};

SRC_ERROR - это просто способ упаковки описания ошибки. Если элемент error всегда хранит указатель на строковый литерал, он должен быть определен как const char *. И наоборот, если в некоторых случаях вы выделяете строку с информацией, указывающей c на фактическую ошибку, например "error allocating 1023 objects\n", то вам нужен либо индикатор, указывающий error, указывает на выделенную память, которая должна быть освобождена после использования, или вы должен всегда выделять память для сообщения об ошибке и всегда освобождать эту память при отбрасывании объекта SRC_ERROR.

typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
    char *entry;
    char md5[MD5_DIGEST_LENGTH];
};

entry должен указывать на выделенную память, и эта память должна быть освобождена при отбрасывании SRC_FILE объект.

typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
    SRC_ERROR error;
    char *name;
    char *full_path;
    SRC_FILE **entries;
    SRC *next_dir;
};
  • name и full_path должны указывать на выделенную память и должны быть освобождены при удалении объекта SRC.
  • next_dir указывает на другой SRC объект, который должен выделяться и последовательно освобождаться.
  • entries указывает на выделенный массив, каждый элемент которого указывает на выделенный объект. Вам нужен способ узнать количество элементов в этом массиве. Вы можете сохранить указатель NULL в конце массива, но для этой информации проще добавить count член в SRC. Также было бы намного проще сделать это указателем на выделенный массив из SRC объектов.

Функция не создает дерево, но пытается создать список каталогов. Всякий раз, когда нужно вернуться в каталог, вы должны добавить новый список из объекта SRC_ERROR, возвращенного scan_source, к списку, уже созданному в объекте SRC_ERROR, выделенном вызывающей стороной, и освободить объект, возвращенный рекурсивным вызовом.

Вот модифицированная версия в тестовой программе:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>

#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
#define MD5_DIGEST_LENGTH  16

#define TRACE(x)  //x

enum { OK = 0, ERROR, OUT_OF_MEMORY };

typedef struct ERROR_STATE ERROR_STATE;
struct ERROR_STATE {
    int code;
    const char *message;  // always a string literal
};

typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
    char *name;        // points to allocated memory
    char md5[MD5_DIGEST_LENGTH];
};

typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
    char *name;         // points to allocated memory
    char *full_path;    // points to allocated memory
    size_t count;       // number of elements in entries
    SRC_FILE *entries;  // allocated array of count elements
    SRC *next_dir;      // the next SRC
};

static char *basename_dup(const char *full_path) {
    char *p = strrchr(full_path, '/');
    return strdup(p ? p + 1 : full_path);
}

/* construct a SRC describing the directory contents.
 * if there is an error, either return a partially constructed SRC or return NULL
 */
SRC *scan_source(const char *source_path, ERROR_STATE *error) {
    char *full_path = strdup(source_path);
    char *name = basename_dup(source_path);
    SRC *source = calloc(1, sizeof(SRC));   // all members initialized to 0

    if (source == NULL) {
        error->code = ERROR;
        error->message = "Unable to allocate memory.\n";
        free(full_path);
        free(name);
        free(source);
        return NULL;
    }

    error->code = OK;
    source->full_path = full_path;
    source->name = name;

    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(source_path))) {
        error->code = ERROR;
        error->message = "Unable to open source directory.\n";
        return source;
    }

    while ((entry = readdir(dir)) != NULL) {
        char path[PATH_MAX];
        int len;

        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;

        len = snprintf(path, sizeof(path), "%s/%s", source_path, entry->d_name);
        if (len >= (int)sizeof(path)) {
            // the path was truncated.
            // you can report this or ignore it...
            TRACE(printf("[%s] - %s - path too long, ignored\n", entry->d_name, path));
            continue;
        }
        if (entry->d_type == DT_DIR) {
            TRACE(printf("[%s] - %s\n", entry->d_name, path));

            SRC *source1 = scan_source(path, error);
            if (error->code != OK) {
                // either ignore the error or abort?
            }
            if (source1) {
                // append the new directory (and its list of sub-directories)
                SRC **tailp = &source->next_dir;
                while (*tailp) tailp = &(*tailp)->next_dir;
                *tailp = source1;
            }
        } else
        if (entry->d_type == DT_REG) {
            TRACE(printf("[FILE] - %s\n", entry->d_name));
            // add the file to the entries list
            SRC_FILE *entries = realloc(source->entries, sizeof(source->entries[0]) * (source->count + 1));
            if (entries == NULL) {
                // you should return to the caller with a proper error code
                error->code = OUT_OF_MEMORY;
                error->message = "cannot reallocate entries array";
                break;
            }
            source->entries = entries;
            // source->entries[count] must point to an allocated object
            name = strdup(entry->d_name);
            if (name == NULL) {
                error->code = OUT_OF_MEMORY;
                error->message = "cannot allocate entry name";
                break;
            }
            source->entries[source->count].name = name;
            memset(source->entries[source->count].md5, 0, sizeof(source->entries[source->count].md5));
            source->count++;
            //if (md5_sum(full_path, source->entries[source->count].md5)) {
            //    // error computing the MD5 sum...
            //}
        }
    }
    closedir(dir);
    return source;
}

void free_source(SRC *source) {
    if (source) {
        free(source->name);
        free(source->full_path);
        for (size_t i = 0; i < source->count; i++) {
            free(source->entries[i].name);
        }
        free(source);
    }
}

int main(int argc, char *argv[1]) {
    ERROR_STATE error = { 0, NULL };

    if (argc < 2) {
        printf("usage: scansource directory [...]\n");
        return 1;
    }

    for (int i = 1; i < argc; i++) {
        SRC *source = scan_source(argv[i], &error);
        if (error.code) {
            printf("Error %d: %s\n", error.code, error.message);
        }
        while (source) {
            SRC *cur = source;
            source = source->next_dir;

            printf("{\n"
                   "  name: '%s',\n"
                   "  full_path: '%s',\n"
                   "  count: %zu,\n"
                   "  entries: [\n",
                   cur->name, cur->full_path, cur->count);
            for (size_t j = 0; j < cur->count; j++) {
                printf("    { md5: '");
                for (size_t k = 0; k < MD5_DIGEST_LENGTH; k++)
                    printf("%02x", cur->entries[j].md5[k]);
                printf("', name: '%s' },\n", cur->entries[j].name);
            }
            printf("  ]\n},\n");
            free_source(cur);
        }
    }
    return 0;
}
0 голосов
/ 24 апреля 2020

Я начал изучать код, и первая проблема, которую я вижу, заключается в том, что вы храните указатели, возвращаемые при вызове readdir() - вам следует скопировать содержащиеся в нем данные.

Изменить

source = add_dir(source, insert_dir_node(entry->d_name, path));

до

source = add_dir(source, insert_dir_node(strdup(entry->d_name), path));

Причина, по которой вы видите ошибки сегментации, заключается в том, что вы всегда пишете после конца source->entries массива.

Сначала вы создаете 0 -size массив:

    int count = 0;
/* ... */
    source->entries = (SRC_FILE **) malloc(sizeof(SRC_FILE*) * count);

Затем установите его 1-й (индексированный 0) элемент:

            source->entries[count]->entry = entry->d_name; //SEGFAULT HERE
            count++;
            source->entries = realloc(source->entries, sizeof(SRC_FILE*)*(count));

Затем вы расширяете массив до 1 элемента, затем записываете во второй индекс и и т. д.

Вы можете либо исправить логи c (всегда выделять место для элементов count+1, потому что вы хотите иметь место не только для существующих, но и для следующих), или, что в этом случае может быть более эффективным, переключитесь также на структуру связанного списка.

Следующая проблема состоит в том, что вы только назначаете указатели на SRC_FILE, а не на структуры SRC_FILE - вы должны изменить определение на:

struct SRC {
    SRC_ERROR error;
    char *name;
    char *full_path;
    SRC_FILE *entries;
    SRC *next_dir;
};

И инициал преобразование в

source->entries = (SRC_FILE *) malloc(sizeof(SRC_FILE) * (count + 1));

Затем критическая часть в

source->entries[count].entry = strdup(entry->d_name);
count++;
source->entries = realloc(source->entries, sizeof(SRC_FILE) * (count + 1));

Есть еще одна вещь, на которую следует обратить внимание: insert_dir_node создает новую структуру SR C, которая должна иметь недавно инициализированный член записи:

next_dir->count = 0;
next_dir->entries = (SRC_FILE *)malloc(sizeof(SRC_FILE) * (1));
  • и, поскольку теперь у нас есть отдельный entries, нам нужно иметь count для каждого из них, поэтому переместите эту переменную в структуру .

Исправление всего этого предоставило мне безошибочную программу.

...