fgets() перезаписывает часть данных структуры

Вопрос или проблема

У меня возникли некоторые трудности с этим проектом кода. Это должно быть связным списком с кучей вымышленных студентов. Я могу правильно ввести первого студента, но когда функция fgets() запускается снова, она перезаписывает все данные в первой структуре, кроме первых 4 символов. Я довольно нов в C.

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

Вот ссылка на код: https://www.onlinegdb.com/EvK1kxplR

А вот и сам код

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

struct student
{
    char name[50];
    int age;
    char status[10];
    char password[25];
    struct student* nextStudent;
} Student;

void AddStudent(struct student** start, char name[50], int age, char status[10], char password[25]);
void InsertStudent(struct student** current, struct student newStu);

int main()
{
    struct student* primaris = NULL;
    struct student primFULL;
    char command[100];
    fgets(command, sizeof(command), stdin);
    if(strcmp(command, "db\n") != 0)return 1;

    while(1)
    {
        memset(command, 0, sizeof(command));
        printf("db> ");
        if(primaris != NULL)
            primFULL = *primaris;
        fgets(command, sizeof(command), stdin);
        switch(tolower(command[0]))
        {
            case 'q': //выход
                return 0;
            case 'h': //помощь
                printf("Добро пожаловать в базу данных студентов COMP-232\nДопустимые команды:\nadd <имя>,<возраст>,<класс>,<пароль>\nгде:\n\tимя - имя студента.\n\tвозраст - возраст студента.\n\tyear - на каком курсе учится студент.\n\tпароль - пароль студента.\ndelete <имя> - Удаляет имя из базы данных.\nlist - Выводит все записи в базе данных.\nprint <имя> - Выводит значения для имени.\nquit - Выход из программы.\nhelp - Отображает это полезное сообщение.\n");
                break;
            case 'a': //добавить
                char* trash = strtok(command, " ");
                char* name = strtok(NULL, ",");
                int age = atoi(strtok(NULL, ","));
                char* stat = strtok(NULL, ",");
                char* pWord = strtok(NULL, ",");
                AddStudent(&primaris, name, age, stat, pWord);
                primFULL = *primaris;
                break;
            case 'l': //список
                struct student* current = primaris;
                while(current != NULL)
                {
                    primFULL = *current;
                    printf("%s", current->name);
                    current = current->nextStudent;
                }
                break;
        }
    }
}

void AddStudent(struct student** start, char name[50], int age, char status[10], char password[25])
{
    struct student temp;
    strncpy(temp.name, name, sizeof(temp.name));
    temp.age = age;
    strncpy(temp.status, status, sizeof(temp.status));
    strncpy(temp.password, strtok(password, "\n"), sizeof(temp.password));
    temp.nextStudent = NULL;
    if(*start == NULL)
    {
        *start = &temp;
        printf("%p", (*start)->nextStudent);
        return;
    }
    while((*start)->nextStudent != NULL)
    {
        if(strcmp(temp.name, (*start)->nextStudent->name) < 0)
        {
            InsertStudent(start, temp);
            return;
        }
        start  = &(*start)->nextStudent;
    }

    return;
}

void InsertStudent(struct student** current, struct student newStu)
{
    newStu.nextStudent = *current;
    (*current)->nextStudent = &newStu;

}

primaris – это начало связного списка, primFULL просто там, потому что я не мог увидеть, что происходит в отладчике, который я использовал.

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

Как уже указал @Gerhard, память должна быть выделена с помощью malloc, поскольку вы хотите, чтобы эта память существовала дольше, чем стековый фрейм функции (что, очевидно, так и есть).

Ошибки, подобные этой, не являются редкостью, особенно когда вы только начинаете. Я бы посоветовал использовать флаг -fsanitize=address или запускать в valgrind, оба из которых уловят эту ошибку. Например, выполнение с -fsanitize=address дает следующую ошибку, указывая точно, что происходит; мы пытаемся использовать старую стековую память после выхода из этой функции.

==216298==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f25ca209030 at pc 0x56b42000f747 bp 0x7ffc2b8abac0 sp 0x7ffc2b8abab0
...(и т.д.)

Ответ или решение

Проблема, с которой вы столкнулись, связана с использованием указателя на переменную, размещённую в стеке (в данном случае, переменную temp внутри функции AddStudent). Когда функция завершает выполнение, память, занимаемая этой переменной, освобождается, и у вас остаётся указатель на недействительный адрес памяти, что и вызывает некорректное поведение вашей программы.

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

struct student {
    char name[50];
    int age;
    char status[10];
    char password[25];
    struct student* nextStudent;
};

void AddStudent(struct student** start, char* name, int age, char* status, char* password);
void InsertStudent(struct student** current, struct student* newStu);

int main() {
    struct student* primaris = NULL;
    char command[100];
    fgets(command, sizeof(command), stdin);
    if (strcmp(command, "db\n") != 0) return 1;

    while (1) {
        memset(command, 0, sizeof(command));
        printf("db> ");
        fgets(command, sizeof(command), stdin);
        switch (tolower(command[0])) {
            case 'q': // quit
                return 0;
            case 'h': // help
                printf("Welcome to COMP-232 Student Database\n"
                       "Valid commands are:\n"
                       "add <name>,<age>,<class>,<password>\n"
                       "delete <name> - Deletes name from database.\n"
                       "list - Lists all records in database.\n"
                       "print <name> - Prints values for name.\n"
                       "quit - Exits program.\n"
                       "help - Displays this helpful message.\n");
                break;
            case 'a': // add
                char* trash = strtok(command, " ");
                char* name = strtok(NULL, ",");
                int age = atoi(strtok(NULL, ","));
                char* stat = strtok(NULL, ",");
                char* pWord = strtok(NULL, ",");
                AddStudent(&primaris, name, age, stat, pWord);
                break;
            case 'l': // list
                struct student* current = primaris;
                while (current != NULL) {
                    printf("%s\n", current->name);
                    current = current->nextStudent;
                }
                break;
        }
    }
}

void AddStudent(struct student** start, char* name, int age, char* status, char* password) {
    struct student* temp = (struct student*)malloc(sizeof(struct student)); // Используем malloc для выделения памяти
    if (!temp) {
        fprintf(stderr, "Ошибка при выделении памяти.\n");
        exit(1);
    }
    strncpy(temp->name, name, sizeof(temp->name));
    temp->age = age;
    strncpy(temp->status, status, sizeof(temp->status));
    strncpy(temp->password, strtok(password, "\n"), sizeof(temp->password));
    temp->nextStudent = NULL;

    if (*start == NULL) {
        *start = temp;
        return;
    }

    struct student* current = *start;
    while (current->nextStudent != NULL) {
        if (strcmp(temp->name, current->nextStudent->name) < 0) {
            InsertStudent(&current->nextStudent, temp);
            return;
        }
        current = current->nextStudent;
    }
    current->nextStudent = temp; // Добавляем в конец списка
}

void InsertStudent(struct student** current, struct student* newStu) {
    newStu->nextStudent = *current;
    *current = newStu;
}

В этом коде используется malloc для выделения памяти для нового студента. Также добавлена проверка на успешное выделение памяти.

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

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

Оцените материал
Добавить комментарий

Капча загружается...