Как хранить данные в динамическом массиве структур?



У меня есть эти структуры, с помощью которых я хотел бы реализовать карту



typedef struct {
const char *name;
int number;
} Entry;

typedef struct {
int available;
int guard;
Entry *entries;
} Map;


И код для работы по инициализации и помещению в него элементов:



Map *map_init() {
Map *res = (Map *) malloc(sizeof(Map));

res->available = 4;
res->guard = 0;
res->entries = (Entry *) malloc(4 * sizeof(Entry));

return res;
}

int map_put(Map *map, const char *name, int nr) {
Entry entry;
int i = 0;

for (i = 0; i < map->guard; ++i) {
entry = map->entries[i];
printf("entry ( x , %u) at %p (%p)n", entry.number, &entry, entry.name);

if (!strcmp(entry.name, name)) // Segmentation fault here
return 0;
}

entry = map->entries[map->guard++];
entry.name = name;
entry.number = nr;

printf("entry (%s, %u) at %p (%p)n", entry.name, entry.number, &entry, entry.name);

return 1;
}


Когда я запускаю свой основной метод



int main(int argc, char **argv) {
printf("Initialising...n");
Map *map = map_init();

printf("Putting...n");
map_put(map, "test", 2);
map_put(map, "some", 1);

// ...

free(map->entries);
free(map);
return 0;
}


Я получаю как выход



Initialising...
Putting...
entry (test, 2) at 0x7fff50b32a90 (0x10f0cdf77)
entry ( x , 0) at 0x7fff50b32a90 (0x5000000000000000)
Segmentation fault: 11


Из чего я мог бы вывести, что ошибка сегментации связана с тем, что entry.name больше не указывает на строку (также теряется число, но это не приводит к несанкционированному доступу к памяти). После того, как я установил данные в первом вызове map_put, все, кажется, хранится в правильных местах.

Кто-нибудь знает, где эти записи могут быть перезаписаны или почему значения не хранятся?

719   3  

3 ответов:

Проблема заключается в следующем:

entry = map->entries[map->guard++];

Вот Вы копия данные из массива в экземпляр структуры entry. Затем вы изменяете данные entry и отбрасываете эти изменения. (Исходные) структурные данные в массиве остаются неизменными.

Это, конечно, приведет к неопределенному поведению, когда вы в следующем вызове map_put используете неинициализированные структуры в массиве.

Либо измените экземпляр структуры массива напрямую и увеличьте map->guard отдельно. Или сделайте entry указатель и наведите его на элемент массива.

Проблема в том, что переменная entry в map_put не является указателем. Это структура. Итак, код

entry = map->entries[map->guard++];
entry.name = name;
entry.number = nr;

Копирует содержимое map->entries[map->guard] в entry. Затем вы обновите поля в entry и вернетесь из функции.

Правильный код выглядит следующим образом

int map_put(Map *map, const char *name, int nr) {
    Entry *entry;  // <-- entry is a pointer
    int i = 0;

    for (i = 0; i < map->guard; ++i) {
        entry = &map->entries[i];
        printf("entry (  x , %u) at %p (%p)\n", entry->number, (void *)entry, (void *)entry->name);

        if (!strcmp(entry->name, name))
            return 0;
    }

    entry = &map->entries[map->guard++];
    entry->name = name;
    entry->number = nr;

    printf("entry (%s, %u) at %p (%p)\n", entry->name, entry->number, (void *)entry, (void *)entry->name);

    return 1;
}

У вас есть серьезная проблема в map_put. Вы используете локальную Entry, в которой Выкопируете записи с карты. Но когда вы позже присваиваете значение локальной копии, исходные записи с карты остаются без изменений.

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

Вместо этого следует использовать Entry *:

int map_put(Map *map, const char *name, int nr) {
    Entry *entry;
    int i = 0;

    for (i = 0; i < map->guard; ++i) {
        entry = map->entries + i;
        printf("entry (  x , %u) at %p (%p)\n", entry->number, entry, entry->name);

        if (!strcmp(entry->name, name))        // Segmentation fault here
            return 0;
    }

    entry = &map->entries[map->guard++];
    entry->name = name;
    entry->number = nr;

    printf("entry (%s, %u) at %p (%p)\n", entry->name, entry->number, entry, entry->name);

    return 1;
}

Но это еще не все. Вы просто храните адрес строки по имени. Это нормально в данном примере, потому что вы фактически передаете строковые литеральные константы. Но если Вы читаете строки из стандартного ввода или файла, содержимое буфера будет перезаписываться с каждым новым значением. Поскольку вы храните только адрес, вы закончите все записи, указывающие на одно и то же значение: последнее.

IMHO вы должны comtemplate с помощью strdup хранить копии строки - и освободить их в конце. Кстати, поскольку у вас есть функция init для инициализации Map, Вы должны постройте очистительный, чтобы все необходимое было свободно в одном месте.

Comments

    Ничего не найдено.