2017-01-18 2 views
2

У меня есть эти структуры, с которыми я хотел бы реализовать картуКак хранить данные в динамическом массиве структур?

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, все, кажется, хранится в правильных местах.

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

+0

Я рекомендую вам прочитать [эту дискуссию о внесении результата 'malloc'] (http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). –

ответ

3

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

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

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

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

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

1

Проблема в том, что переменная 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; 
} 
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; 
} 

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

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