C - fclose() 触发断点
C - fclose() triggers a breakpoint
我正在编写一个函数 (*rwObjects()
),它将读取一个格式化文件并保存它的字符串,一次一个对象。不幸的是,它对我的学习有限制 - stdio.h、stdlib.h 和 string.h 实际上是我可以使用的所有内容。
问题是:每当我 运行 代码到达 fclose(input)
时,VS17 说我的项目触发了一个断点,然后打开一个显示 "wntdll.pdb not loaded" 的选项卡什么的。
问题是:如何不触发断点并正常关闭文件?或者,如果问题不在文件中,它在哪里?
代码 (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("");
}
这是一个令人困惑的程序,但我找不到其他方法来符合该死的规则,所以让我们把编码风格放在一边,好吗?
此外,我知道如果 运行 成功,则不会发生任何事情 - 这是设计使然。还没完。
编辑:不用担心输入数据的有效性。所有输入数据格式都在任务中明确说明,因此不需要检查。不过,出于好奇,这里是:
objects.txt:
3
USA WelfareArrangement 120
Rus PoiskZemli 30
usa asdfEstate 1
编辑 2:当我停止使用 malloc 时,一切都很好。问题是 - 为什么它确实是这样一个问题,如果不使用 malloc,我将如何创建一个我需要的确切大小的数组,而不是创建最大大小的数组?
第一个问题:
objectType *objectList = (objectType *)malloc(atoi(objectQttStr)), *currentObject = objectList;
currentObject = (objectType *)malloc(atoi(objectQttStr));
malloc
函数分配给定数量的字节。所以如果你有 5 个对象,你只分配 5 个字节。这对您的结构来说还不够。这会导致您写入已分配内存的末尾,调用 undefined behavior.
如果你想让它为特定数量的对象分配space,你需要乘以对象大小:
objectType *objectList = malloc(sizeof(*objectList)*atoi(objectQttStr));
另外,don't cast the return value of malloc
.
您还为 currentObject
分配了与 objectList
相同的值,但随后用单独的内存分配覆盖了它。所以去掉第二个 malloc
.
第二题:
memset(&strBuf, 0, sizeof(strBuf));
copyInputStr(&strBuf, &j, &objectStr[j], inputType);
writeObject(&strBuf, currentObject, inputType);
您的 copyInputStr
和 writeObject
函数需要一个 char *
,但是您传入了类型为 char (*)[30]
的 strBuf
数组的地址。在此处删除地址运算符:
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 中的字符串是一个以 null 结尾的字符数组,因此您最终得到的不是字符串,而只是一个字符数组。当您稍后在此数组上调用 strcpy
时,该函数找不到空字节,因此它会一直读取直到找到为止。这导致函数读取未初始化的字节 and/or 读取数组末尾,这再次调用未定义的行为。
在循环后添加空终止字节。另外,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;
}
我正在编写一个函数 (*rwObjects()
),它将读取一个格式化文件并保存它的字符串,一次一个对象。不幸的是,它对我的学习有限制 - stdio.h、stdlib.h 和 string.h 实际上是我可以使用的所有内容。
问题是:每当我 运行 代码到达 fclose(input)
时,VS17 说我的项目触发了一个断点,然后打开一个显示 "wntdll.pdb not loaded" 的选项卡什么的。
问题是:如何不触发断点并正常关闭文件?或者,如果问题不在文件中,它在哪里?
代码 (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("");
}
这是一个令人困惑的程序,但我找不到其他方法来符合该死的规则,所以让我们把编码风格放在一边,好吗?
此外,我知道如果 运行 成功,则不会发生任何事情 - 这是设计使然。还没完。
编辑:不用担心输入数据的有效性。所有输入数据格式都在任务中明确说明,因此不需要检查。不过,出于好奇,这里是:
objects.txt:
3
USA WelfareArrangement 120
Rus PoiskZemli 30
usa asdfEstate 1
编辑 2:当我停止使用 malloc 时,一切都很好。问题是 - 为什么它确实是这样一个问题,如果不使用 malloc,我将如何创建一个我需要的确切大小的数组,而不是创建最大大小的数组?
第一个问题:
objectType *objectList = (objectType *)malloc(atoi(objectQttStr)), *currentObject = objectList;
currentObject = (objectType *)malloc(atoi(objectQttStr));
malloc
函数分配给定数量的字节。所以如果你有 5 个对象,你只分配 5 个字节。这对您的结构来说还不够。这会导致您写入已分配内存的末尾,调用 undefined behavior.
如果你想让它为特定数量的对象分配space,你需要乘以对象大小:
objectType *objectList = malloc(sizeof(*objectList)*atoi(objectQttStr));
另外,don't cast the return value of malloc
.
您还为 currentObject
分配了与 objectList
相同的值,但随后用单独的内存分配覆盖了它。所以去掉第二个 malloc
.
第二题:
memset(&strBuf, 0, sizeof(strBuf));
copyInputStr(&strBuf, &j, &objectStr[j], inputType);
writeObject(&strBuf, currentObject, inputType);
您的 copyInputStr
和 writeObject
函数需要一个 char *
,但是您传入了类型为 char (*)[30]
的 strBuf
数组的地址。在此处删除地址运算符:
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 中的字符串是一个以 null 结尾的字符数组,因此您最终得到的不是字符串,而只是一个字符数组。当您稍后在此数组上调用 strcpy
时,该函数找不到空字节,因此它会一直读取直到找到为止。这导致函数读取未初始化的字节 and/or 读取数组末尾,这再次调用未定义的行为。
在循环后添加空终止字节。另外,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;
}