C - fclose() запускает точку останова

Я пишу функцию (*rwObjects()), которая будет читать отформатированный файл и сохранять его строки, по одному объекту за раз. К сожалению, для моих исследований есть ограничение - stdio.h, stdlib.h и string.h - это практически все, что я могу использовать.

Вот проблема: всякий раз, когда я запускаю код, когда он доходит до fclose(input), VS17 говорит, что мой проект активировал точку останова, а затем открывает вкладку с надписью «wntdll.pdb не загружен» или что-то в этом роде.

Вопрос в следующем: как мне не активировать точку останова и правильно закрыть файл? Или, если проблема не в файле, то где она?

Код (С):

#define _CRT_SECURE_NO_WARNINGS
#define cnCOUNTRY_LENGTH 3
#define cnOBJECT_NAME_LENGTH 30
#define cnOBJECT_MAX 1000

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

//--Поле объекта objectType--
//Country (0)       - строка названия страны
//ObjectName (1)    - строка названия объекта
//Square (2)        - площадь объекта
//Error (3)         - ошибка в содержании строки
typedef enum IOOptions {Country, ObjectName, Square, Error} IOType;

//--Тип обрабатываемых объектов--
//char Country      - строка названия страны
//char ObjectName   - строка названия объекта
//int Square        - площадь объекта
typedef struct object {
    char Country[cnCOUNTRY_LENGTH];
    char ObjectName[cnOBJECT_NAME_LENGTH];
    int Square;
} objectType;

//--Копирование текущего элемента строки objects.txt--
//strMod            - Строка, в которую идёт копирование
//strPos            - Позиция в считываемой строке
//strBlueprint      - Строка, из которой идёт копирование
//writeType         - Поле объекта objectType. При "Country" - переводит вводимые символы в верхний регистр ('a' -> 'A' и т.д.)
void copyInputStr(char *strMod, int *strPos, char *strBlueprint, IOType writeType) {
    for (*strPos; *strBlueprint != ' ' && *strBlueprint != '\n' && *strBlueprint != NULL; *strPos = *strPos + 1) {
        *strMod = *strBlueprint;
        if (writeType == Country) toupper(*strMod);
        strBlueprint++; strMod++;
    }
}

//--Запись текущего элемента строки objects.txt в текущий объект--
//strInput          - Строка, из которой идёт запись
//objectOutput      - Объект, в который идёт запись
//writeType         - Поле объекта, в которое идёт запись
void writeObject(char *strInput, objectType *objectOutput, IOType writeType) {
    if (writeType == Country)
        strcpy(objectOutput->Country, strInput);
    else if (writeType == ObjectName)
        strcpy(objectOutput->ObjectName, strInput);
    else if (writeType == Square)
        objectOutput->Square = atoi(strInput);
    else printf("Error 1. Invalid parameters");
}

//--Чтение objects.txt и запись в массив objectType--
//Возвращает указатель на первый элемент массива объектов
objectType *rwObjects() {
    FILE *input = fopen("objects.txt", "r");
    char objectQttStr[4], objectStr[38];
    fgets(objectQttStr, 4, input);
    objectType *objectList = (objectType *)malloc(atoi(objectQttStr)), *currentObject = objectList;
    currentObject = (objectType *)malloc(atoi(objectQttStr));
    for (int i = 0; i < atoi(objectQttStr); i++) {
        fgets(objectStr, 38, input);
        IOType inputType = Country;
        for (int j = 0; objectStr[j] != NULL && objectStr[j] != '\n'; j++) {
            char strBuf[cnOBJECT_NAME_LENGTH];
            memset(&strBuf, 0, sizeof(strBuf));

            copyInputStr(&strBuf, &j, &objectStr[j], inputType);

            writeObject(&strBuf, currentObject, inputType);

            inputType++; 
        }
        currentObject++;
    }
    fclose(input);         //this is where it happens
    return objectList;
}

void main() {
    objectType *objectList = rwObjects();
    printf("");
}

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

Кроме того, я знаю, что если он запустится успешно, ничего не произойдет - так задумано. Это еще не закончено.

РЕДАКТИРОВАТЬ: Не беспокойтесь о достоверности входных данных. Все форматирование входных данных явно указано в задании, поэтому никаких проверок не требуется. Тем не менее, для любопытных, вот:

объекты.txt:

3
USA WelfareArrangement 120
Rus PoiskZemli 30
usa asdfEstate 1

РЕДАКТИРОВАТЬ 2: В тот момент, когда я перестал использовать malloc, все было в порядке. Вопрос в том, почему именно это было такой проблемой, и как мне создать массив точного размера, который мне нужен, вместо создания максимального размера каждый раз, если не с помощью malloc?


person Johnny Cache    schedule 19.12.2017    source источник
comment
Я предполагаю, что у вас где-то установлена ​​точка останова, возможно, внутри кода стандартной библиотеки. Есть ли способ в VS17 просмотреть/перечислить все точки останова?   -  person Oliver Charlesworth    schedule 19.12.2017
comment
objectStr[j] != NULL выглядит так, будто код сравнивает char с указателем. Вместо этого порекомендуйте objectStr[j] != '\0'.   -  person chux - Reinstate Monica    schedule 19.12.2017
comment
@OliverCharlesworth Пытался. Только перечисляет точки останова в моем проекте, а не во внешних файлах. Если они и есть, то не я их туда положил. И нет, это не сработает, если я избавлюсь от всех своих точек останова.   -  person Johnny Cache    schedule 19.12.2017
comment
Интересно, есть ли запись за пределы, искажающая input, может быть, в copyInputStr?   -  person David Schwartz    schedule 19.12.2017
comment
Ваше распределение памяти не имеет смысла. Кажется, вы выделяете массив структур, но вы даете им только один байт каждой. Затем вы назначаете текущий указатель, который затем перезаписываете новым бесполезным выделением.   -  person Lee Daniel Crocker    schedule 19.12.2017
comment
Код должен проверять возвращаемое значение fgets(objectStr, 38, input);.   -  person chux - Reinstate Monica    schedule 19.12.2017
comment
Какой бы уровень предупреждения вы ни использовали, он недостаточно высок. Пример предупреждений от clang здесь.   -  person WhozCraig    schedule 19.12.2017
comment
Разместите свой образец ввода. fgets(objectQttStr, 4, input); выглядит довольно маленьким для целого числа.   -  person chux - Reinstate Monica    schedule 19.12.2017
comment
Я думаю, вы сталкиваетесь с ошибкой утверждения или VS, а не с точкой останова. Я не использую VS, поэтому я не могу смотреть дальше, но я подозреваю, что вы каким-то образом испортили указатель ввода, и fclose жалуется.   -  person Erik Johnson    schedule 19.12.2017
comment
@LeeDanielCrocker Как мне назначить им один байт? Я думал, что назначаю их ровно столько, чтобы содержать число objectQttStr структур objectType. И VS вроде согласен, судя по окну контрольных значений.   -  person Johnny Cache    schedule 19.12.2017
comment
Я предполагаю, что objQttStr означает что-то вроде количества объектов? Он передается в malloc напрямую. Аргумент malloc — это количество байтов, а не количество объектов. Возможно, вы хотели использовать calloc?   -  person Lee Daniel Crocker    schedule 19.12.2017
comment
@LeeDanielCrocker objQttStr передается в atoi(). Итак, значение передано. Честно говоря, я не совсем уверен, как работает malloc, но, как я это вижу, он выделяет (тип *) malloc (количество), что означает, что он выделяет точно тип, умноженный на количество памяти. По крайней мере, это верно для любого другого типа. Возможно, проблема заключается в использовании структуры.   -  person Johnny Cache    schedule 19.12.2017
comment
Неа. Malloc выделяет байты, и точка. Вам нужно сделать это умножение самостоятельно, иначе вы будете гадить по всей нераспределенной памяти и вызывать ошибки ... эй ...   -  person Lee Daniel Crocker    schedule 19.12.2017
comment
@LeeDanielCrocker Интересно. Так что то, чему нас учат в нашем университете, — ложь. Я постараюсь избежать выделения странных вещей и посмотрю, сработает ли это. Также c может гореть в аду.   -  person Johnny Cache    schedule 19.12.2017


Ответы (1)


Первая проблема:

objectType *objectList = (objectType *)malloc(atoi(objectQttStr)), *currentObject = objectList;
currentObject = (objectType *)malloc(atoi(objectQttStr));

Функция malloc выделяет заданное количество байтов. Итак, если у вас есть 5 объектов, вы выделяете только 5 байтов. Этого недостаточно для ваших структур. Это приводит к тому, что вы пишете за пределами выделенной памяти, что вызывает неопределенное поведение.

Если вы хотите, чтобы он выделял место для определенного количества объектов, вам нужно умножить на размер объекта:

objectType *objectList = malloc(sizeof(*objectList)*atoi(objectQttStr));

Кроме того, не преобразуйте возвращаемое значение malloc.

Вы также присваиваете currentObject то же значение, что и objectList, но затем перезаписываете его отдельным выделением памяти. Так что избавьтесь от второго malloc.

Вторая проблема:

        memset(&strBuf, 0, sizeof(strBuf));

        copyInputStr(&strBuf, &j, &objectStr[j], inputType);

        writeObject(&strBuf, currentObject, inputType);

Ваши функции copyInputStr и writeObject ожидают char *, но вы передаете адрес массива strBuf, который имеет тип char (*)[30]. Избавьтесь от оператора адреса здесь:

        memset(strBuf, 0, sizeof(strBuf));

        copyInputStr(strBuf, &j, &objectStr[j], inputType);

        writeObject(strBuf, currentObject, inputType);

Третья проблема:

void copyInputStr(char *strMod, int *strPos, char *strBlueprint, IOType writeType) {
    for (*strPos; *strBlueprint != ' ' && *strBlueprint != '\n' && *strBlueprint != NULL; *strPos = *strPos + 1) {
        *strMod = *strBlueprint;
        if (writeType == Country) toupper(*strMod);
        strBlueprint++; strMod++;
    }
}

Когда вы копируете символы в strMod, вы не добавляете нулевой байт в конце. Строка в C представляет собой массив символов, заканчивающийся нулем, поэтому в итоге вы получите не строку, а просто массив символов. Когда вы позже вызовете strcpy для этого массива, функция не найдет нулевой байт, поэтому она продолжит чтение, пока не найдет. Это приводит к тому, что функция считывает неинициализированные байты и/или читает дальше конца массива, что снова приводит к неопределенному поведению.

Добавьте завершающий нулевой байт после цикла. Кроме того, результат функции toupper ничему не присваивается, поэтому он ничего не делает. Вам нужно назначить его обратно на *strMod:

void copyInputStr(char *strMod, int *strPos, char *strBlueprint, IOType writeType) {
    for (*strPos; *strBlueprint != ' ' && *strBlueprint != '\n' && *strBlueprint != NULL; *strPos = *strPos + 1) {
        *strMod = *strBlueprint;
        if (writeType == Country) *strMod = toupper(*strMod);
        strBlueprint++; strMod++;
    }
    *strMod = 0;
}
person dbush    schedule 19.12.2017
comment
Вы, сэр, удивительны. Вы не только максимально подробно ответили на мой вопрос, но и решили несколько проблем, с которыми я столкнулся сам (хотя я их тем временем и разобрался). Вы заслуживаете бесконечной похвалы. - person Johnny Cache; 19.12.2017