Динамически изменяемые массивы в языке Си


Я пытаюсь выучить C, используя http://c.learncodethehardway.org / но я застрял на одном из дополнительных кредитных вопросов в главе 18 (http://c.learncodethehardway.org/book/learn-c-the-hard-waych18.html и я надеюсь, что кто-нибудь сможет мне помочь.

Специфическая проблема, с которой я сталкиваюсь, заключается в том, что есть несколько структур, определенных следующим образом:

#define MAX_ROWS = 500;
#define MAX_DATA = 512;

struct Address {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

struct Database {
    struct Address rows[MAX_ROWS];       
};     

struct Connection {
    FILE *file;
    struct Database *db;
}; 
Задача состоит в том, чтобы переработать это так, чтобы rows мог иметь переменный размер, который не зависит от этой константы.

Итак в моем методе Database_create я пытаюсь инициализировать rows следующим образом:

conn->db->rows = (struct Address*) malloc(max_rows * sizeof(struct Address));

Где conn->db указывает на экземпляр базы данных, а max_rows - это int, который передается функции. Я также изменил структуру базы данных на

struct Database{
    struct Address* rows;
}

Этот бит кода, кажется, работает нормально, но если я пытаюсь получить доступ к любому члену rows, я получаю ошибку сегментации, которая, по моему мнению, означает, что я пытаюсь получить доступ к битам памяти, которые не используются.

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


EDIT: просто хотел добавить некоторые дополнительные детали к этому после запуска его с Valgrind, что вызывает ошибку:

==11972== Invalid read of size 4
==11972==    at 0x100001578: Database_set (ex18.c:107)
==11972==    by 0x100001A2F: main (ex18.c:175)
==11972==  Address 0x7febac00140c is not stack'd, malloc'd or (recently) free'd

Строка кода, на которую он указывает:

struct Address *addr = &conn->db->rows[id];
if(addr->set) die("Already set, delete it first");   

Строка 107-это if(addr->set), что, как я предполагаю, означает, что он пытается прочитать то, что не может

3 3

3 ответа:

Когда вы загружаете

void Database_load(struct Connection *conn)
{
        int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
        if(rc != 1) die("Failed to load database");
}

Или запись базы данных,

void Database_write(struct Connection *conn)
{
        rewind(conn->file);

        int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
        if(rc != 1) die("Failed to write database");

        rc = fflush(conn->file);
        if(rc == -1) die("Cannot flush database.");
}

Вы не читаете и не записываете содержимое (struct Address es), а только указатель на место в памяти. При чтении ранее записанной базы данных этот указатель не указывает на что-либо конкретное, это дикий указатель. Тогда, конечно, попытка разыменования очень вероятно вызовет ошибку сегментации, если, возможно, это не так, вы получите бессмысленные псевдоданные.

Если вы измените свой struct Database так что rows является struct Address*, вам нужно сохранить количество элементов и изменить код чтения и записи, чтобы также обрабатывать данные, на которые указывает rows. Сначала напишите, сколько элементов у вас есть, а затем напишите остальные (max_data, max_rows а при чтении считывайте, сколько у вас предметов, выделяйте для них место, читайте max_data и max_rows и предметы.

Вы хотите sizeof(struct Address) не sizeof(struct Address*)

sizeof(struct Address*) скорее всего, возвращается размер 4 (полностью зависит от целевой платформы), тогда как фактический размер структуры Address ближе к 1040 (предполагая 1 байт на char и 4 на int)

EDIT : Ну, похоже (с вашим последним редактированием), вы действительно делаете эту часть правильно.

На самом деле вы не выделяете достаточный размер для адресных структур. Вам нужно что-то вроде этого:

struct Database{
    struct Address** rows;
}
//create array of pointers to Address structures
// (each element has size of the pointer)
conn->db->rows = (struct Address**) malloc(max_rows * sizeof(struct Address*));
for(int i=0; i < max_rows; i++) {
    conn->db->rows[i] = (struct Address*) malloc(sizeof(struct Address));
}

Или вот это:

struct Database{
    struct Address* rows;
}
//create array of Address structures
// (each element has size of the structure)
conn->db->rows = (struct Address*) malloc(max_rows * sizeof(struct Address));