I'm writing a function (*rwObjects()
) that will read a formatted file and save it's strings, one object at a time. Unfortunately, it's for my studies which have a restriction - stdio.h, stdlib.h and string.h is practically all I can use.
Here's the problem: whenever I run the code, when it gets to the fclose(input)
, VS17 says my project's triggered a breakpoint, and then opens a tab that says "wntdll.pdb not loaded" or something.
The question is: how do I not trigger the breakpoint and close the file properly? Or, if the problem isn't in the file, where is it?
Code (C):
#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("");
}
This is one confusing program, but I've found no other way to conform the bloody rules, so let's put the coding style aside, shall we?
Also, I know that should it run successfully, nothing would happen - that is by design. It's not finished yet.
EDIT: Don't worry about validity of input data. All the input data formatting is explicitly stated in the task, ergo no checks are reqired. Still, for the curious, here it is:
objects.txt:
3
USA WelfareArrangement 120
Rus PoiskZemli 30
usa asdfEstate 1
EDIT 2: The moment I stopped using malloc, everything was fine. The question is - why exactly was it such a problem, and how would I create an array of the exact size I need, instead of creating max size evey time, if not with malloc?
First problem:
objectType *objectList = (objectType *)malloc(atoi(objectQttStr)), *currentObject = objectList;
currentObject = (objectType *)malloc(atoi(objectQttStr));
The malloc
function allocates a given number of bytes. So if you have 5 objects, you only allocate 5 bytes. That's not enough for your structs. This results in you writing past the end of allocated memory which invokes undefined behavior.
If you want it to allocate space for a specific number of objects, you need to multiply by the object size:
objectType *objectList = malloc(sizeof(*objectList)*atoi(objectQttStr));
Also, don't cast the return value of malloc
.
You also assign currentObject
to the same value as objectList
but then overwrite it with a separate memory allocation. So get rid of the second malloc
.
Second problem:
memset(&strBuf, 0, sizeof(strBuf));
copyInputStr(&strBuf, &j, &objectStr[j], inputType);
writeObject(&strBuf, currentObject, inputType);
Your copyInputStr
and writeObject
function expect a char *
, but you pass in the address of the strBuf
array which has type char (*)[30]
. Get rid of the address-of operator here:
memset(strBuf, 0, sizeof(strBuf));
copyInputStr(strBuf, &j, &objectStr[j], inputType);
writeObject(strBuf, currentObject, inputType);
Third problem:
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++;
}
}
When you copy the characters in strMod
, you're not adding a null byte at the end. A string in C is a null-terminated array of characters, so what you end up with is not a string but just an array of characters. When you later call strcpy
on this array, the function doesn't find a null byte so it keeps reading until it does. This causes the function to read uninitialized bytes and/or read past the end of the array, which again invokes undefined behavior.
Add the null terminating byte after the loop. Also, the result of the toupper
function isn't assigned to anything, so it does nothing. You need to assign it back to *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;
}