Ошибка сегментации при записи данных в динамический массив

Моя задача — написать файл, отображающий неизвестное количество записей, введенных пользователем. Каждая запись имеет следующие поля: Имя, Фамилия, Адрес, Город, Штат, Почтовый индекс и Номер телефона.

Я предположил, что лучший способ сделать это — определить структуру Record с указанными выше полями, а затем объявить массив Record, который будет содержать столько записей, сколько ввел пользователь. Для этого я бы использовал цикл для получения входных данных для каждого поля для каждой записи, а затем, если пользователь хочет продолжить, динамически выделять дополнительное пространство в массиве Record и продолжать до тех пор, пока пользователь не введет no. Я столкнулся с ошибкой записи местоположения нарушения прав доступа в строке:

scanf("%s", records[i]->fname);

Что не так с моим кодом?

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
struct Record;

struct Record
    {
        char fname[51];
        char lname[51];
        char address[51];
        char city[51];
        char state[51];
        int zipcode;
        int phoneNumber;
    };

int main()
{
    FILE *fileWriter;
    const char filename[] = "data.txt";
    char answer = 'y';
    int size = 1;
    int i = 0;
    struct Record **records;
    records = malloc(sizeof(*records)*(size));

    while(answer == 'y' || answer == 'Y')
    {
        printf("First Name: \n");
        scanf("%s", records[i]->fname);

        printf("Last Name: \n");
        scanf("%s", records[i]->lname);

        printf("Address: \n");
        scanf("%s", records[i]->address);

        printf("City: \n");
        scanf("%s", records[i]->city);

        printf("State: \n");
        scanf("%s", records[i]->state);

        printf("Zipcode: \n");
        scanf("%d", records[i]->zipcode);

        printf("Phone Number: \n");
        scanf("%d", records[i]->phoneNumber);
        //stores all record info

        printf("Are there anymore records? [y/n] ");
        answer = getchar();
        if(answer == 'y' || answer == 'Y')
        {
            size++;
            records[i++];
            printf("\n");
        }
        records = realloc(records,sizeof(*records)*(size));
    }

    //open file
    fileWriter = fopen(filename,"wb");

    if(fileWriter != NULL)
    {
        if(fwrite(records,sizeof(*records),size,fileWriter) != 1)
        {
            fprintf(stderr, "Failed to write to %s\n", filename);
            exit(1);
        }
        fclose(fileWriter);
    }
    else
    {
        printf("Error opening file.");
    }
}

ОТРЕДАКТИРОВАННАЯ ВЕРСИЯ

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

struct Record
    {
        char fname[51];
        char lname[51];
        char address[51];
        char city[51];
        char state[51];
        int zipcode;
        int phoneNumber;
    };




int main()
{
    FILE *fileWriter;
    const char filename[] = "data.txt";
    char answer = 'y';
    int size = 1;
    int i = 0;
    struct Record *records = NULL;
    struct Record *records_temp;




    while(answer == 'y' || answer == 'Y')
    {
        struct Record *records_temp = realloc(records,(size)*sizeof(*records));

        if(records_temp == NULL)  
        {
            free(records); 

        }
        records = records_temp;
        printf("First Name: \n");
        scanf("%s", records[i].fname);
        printf("Last Name: \n");
        scanf("%s", records[i].lname);

        printf("Address: \n");
        scanf(" %[^\n]", records[i].address);

        printf("City: \n");
        scanf("%s", records[i].city);

        printf("State: \n");
        scanf("%s", records[i].state);

        printf("Zipcode: \n");
        scanf("%d", &records[i].zipcode);

        printf("Phone Number: \n");
        scanf("%d", &records[i].phoneNumber);
        //stores all record info

        printf("Are there anymore records? [y/n] ");
        answer = getchar();
        if(answer == 'y' || answer == 'Y')
        {
            size++;
            records[i++];
            printf("\n");
        }

        //open file

    fileWriter = fopen(filename,"wb");

    if(fileWriter != NULL)
    {
        if(fwrite(records,sizeof(*records),size,fileWriter) != 1)
        {
            fprintf(stderr, "Failed to write to %s\n", filename);
            exit(1);
        }
        fclose(fileWriter);
    }
    else
    {
        printf("Error opening file.");
    }
}
}

person Karlioh    schedule 26.05.2015    source источник
comment
Обратите внимание, что строка records[i++]; увеличивает i и не делает больше ничего полезного.   -  person Jonathan Leffler    schedule 26.05.2015
comment
Также обратите внимание, что строка struct Record; на самом деле не нужна. Единственный раз, когда это может иметь значение, - это если вы определяете взаимно рекурсивные структуры в области действия функции, а не в области файла (и это использование находится в области файла). Как бы то ни было, строка говорит, что существует тип struct Record, а следующий блок кода говорит, что существует тип struct Record, и вот как он определен.   -  person Jonathan Leffler    schedule 26.05.2015
comment
records[i]->fname означает (*records[i]).fname, что означает (*(*(records + i))).fname. На что указывает records + i? На что указывает *(records + i)?   -  person user253751    schedule 26.05.2015
comment
@JonathanLeffler, обратите внимание, что вопрос был Что не так с моим кодом?, и ваши комментарии являются ответами на этот вопрос. Сравните их с комментарием иммибиса. Пожалуйста, рассмотрите возможность удаления ваших комментариев и публикации ответа.   -  person user1717828    schedule 26.05.2015
comment
@ user1717828: Мои комментарии останутся комментариями. В основном это незначительные проблемы. Основная проблема заключается в неправильном распределении памяти, описанном schlezzz15 в его ответ.   -  person Jonathan Leffler    schedule 26.05.2015
comment
@immibis Я пытаюсь сделать записи массивом и динамически выделять память, чтобы определить размер и постоянно увеличивать его.   -  person Karlioh    schedule 26.05.2015
comment
@Karlioh, Кроме того, вам нужно & перед именем переменной при сканировании %d.   -  person Spikatrix    schedule 26.05.2015
comment
@JonathanLeffler, не могли бы вы привести пример для Единственный случай, когда это может иметь значение, - это если вы определяете взаимно рекурсивные структуры в области функции, а не в области файла (и это использование относится к области файла)?   -  person Spikatrix    schedule 26.05.2015
comment
@CoolGuy: struct A{ … }; void f(void) { struct A; struct B { …; struct A *a_ref; … }; struct A { …; struct B *b_ref; … }; … } — без struct A; элемент a_ref указывал бы на структуру внешне определенного типа, а не на взаимно рекурсивную пару типов структур. Сообщения об ошибках тоже могут быть довольно запутанными!   -  person Jonathan Leffler    schedule 26.05.2015
comment
@CoolGuy Я отредактировал свой код, внеся несколько исправлений, касающихся памяти, и больше не получаю нарушения прав доступа, однако, похоже, я получаю странную ошибку после попытки ввести адрес для записи. Адрес имеет 2 пробела между строками, но при вводе это, кажется, вызывает пропуск при запросе пользовательского ввода, по одному на пробел. Например, когда я ввожу только адрес #, пропуск не происходит, но когда я ввожу адрес # с названием улицы и пробелами, он печатает подсказку для города и штата, а также почтовый индекс, но получает ввод только для почтового индекса.   -  person Karlioh    schedule 27.05.2015
comment
@Karlioh, это потому, что %s не будет сканировать пространство и прекратит сканирование, когда увидит одно из них. Используйте %[^\n] вместо %s, если вы хотите получить линейный ввод.   -  person Spikatrix    schedule 27.05.2015
comment
@CoolGuy Я внес это изменение, и теперь происходит то же самое, но для строки текста я использую %[^\n].   -  person Karlioh    schedule 27.05.2015
comment
@Karlioh, попробуйте добавить пробел перед %[^\n].   -  person Spikatrix    schedule 27.05.2015
comment
@CoolGuy Хорошо, теперь программа работает без ошибок, но в файле результатов написано куча дерьма, странные символы. И вдобавок ко всему, как только программа спрашивает пользователя, есть ли у него еще записи, программа завершает работу.   -  person Karlioh    schedule 27.05.2015
comment
@Karlioh, я понятия не имею о fwrite. Вы можете опубликовать новый вопрос, указав необходимые детали. Надеюсь, кто-нибудь на него ответит.   -  person Spikatrix    schedule 27.05.2015


Ответы (3)


Что ж, вы получаете segfault, потому что вы не выделили память для первого объекта в вашем records.

Итак, чтобы решить, что вам нужно

records[size-1] = malloc(sizeof(Records));

Скажем так: у вас есть records, который является указателем на указатель на Records. Когда ты сделал

records = malloc(sizeof(*records)*(size));

На самом деле вы запросили size указателей на Records. Но этого недостаточно, вам нужно выделить другую память для хранения фактического Records, поэтому мы должны

records[size - 1] = malloc(sizeof(Records));

Примечание: если size > 1, вам следует сделать следующее:

int i = 0;
for(;i < size; i++) {
    records[i] = malloc(sizeof(Records));
}

В дополнение к этому, почему вы выбрали Records **, как уже объяснил Арджун, вы должны использовать Records * и исправить часть realloc новой памяти, потому что, если realloc терпит неудачу, она возвращает NULL, и вы в конечном итоге с утечкой памяти или другим segfault в худшем случае, в любом случае - это не хорошо для вашей программы.

См. сообщение Арджуна

person boaz_shuster    schedule 26.05.2015

Если вы хотите динамически выделить место для списка Record, вы должны сделать:

struct Record *records;
records = malloc(size * sizeof(*records));

Это выделяет место для size количества Records.

Чтобы увеличить выделенный размер, вы должны:

struct Record *records_temp = realloc(records, newsize * sizeof(*records));

if (records_temp == NULL) {
    free(records);
    /* die with error -ENOMEM */
}

records = records_temp;

Не realloc к одному и тому же указателю. Это может привести к утечке памяти при сбое.

Или вы можете не использовать malloc() и использовать только realloc() в цикле, предварительно предоставив ему указатель NULL.

Стандарты C 89 говорят:

4.10.3.4 Функция перераспределения

Если ptr является нулевым указателем, функция realloc ведет себя как функция malloc для указанного размера.

struct Record *records = NULL;
struct Record *records_temp;
size = INITIAL_SIZE;

while (/* your condition */) {
    records_temp = realloc(records, size * sizeof(*records));

    if (records_temp == NULL) {
        free(records);
        /* die with error -ENOMEM */
    }

    records = records_temp;

    /* do stuff */

    size += SIZE_INCREMENT;
}
person Arjun Sreedharan    schedule 26.05.2015
comment
красиво написано! получил мой голос + добавил вас в мой пост! - person boaz_shuster; 26.05.2015

Как Джонатан Леффлер прокомментировано, но отказался делать ответ из своих комментариев:

Обратите внимание, что строка records[i++]; увеличивает i и не делает больше ничего полезного.

И также:

Также обратите внимание, что строка struct Record; на самом деле не нужна. Единственный раз, когда это может иметь значение, - это если вы определяете взаимно рекурсивные структуры в области действия функции, а не в области файла (и это использование находится в области файла). Как бы то ни было, строка говорит «есть тип struct Record», а следующий блок кода говорит «есть тип struct Record, и вот как он определен».

Когда спрашивает Крутой парень, чтобы проиллюстрировать, что имелось в виду, Джонатан сказал:

struct A { … };
struct B { … };
void f(void)
{
    struct A;
    struct B
    {
         …;
        struct A *a_ref;
        …
    };
    struct A
    {
        …;
        struct B *b_ref;
        …
    };
    …
}

Без строки struct A; элемент a_ref указывал бы на структуру внешне определенного типа struct A, а не на взаимно рекурсивную пару структурных типов. Сообщения об ошибках тоже могут быть довольно запутанными! Однако повторное использование таких имен типов — плохая идея.

person Community    schedule 26.05.2015