fread() 中极其奇怪的行为;打印字符串时读取整个字符串,但说它没有达到 EOF,而当它没有达到 EOF 时
Extremely weird behavior in fread(); read entire string when printing string but says it did not reached EOF and reaches EOF when it did not
我在以下函数中遇到了一些奇怪的行为:
char* read_file(const char* filename) {
FILE* file = fopen(filename, "rb"); // error check
fseek(file, 0L, SEEK_END); // error check this too
unsigned int size = ftell(file); // error check
rewind(file);
// I know a char is a single byte, but just for readibility's sake:
char* buffer = malloc(sizeof(char) * size + 1); // error check
unsigned int result = fread(buffer, sizeof(char), size, file); // more error check
fclose(file);
// puts(buffer); // see the rest of the question, this is line 16
return buffer;
}
如果第 16 行保持注释,我的程序中出现未定义的行为,即返回的字符串不是我预期的,即发生错误。通过错误检查,我避免了这个问题,我得到 result
等于 size
,所以 fread 成功读取了文件。现在,如果我取消注释第 16 行并打印结果,一切正常并且不会发生未定义的行为。为什么会发生这种情况?
malloc
分配未初始化的存储,这意味着返回缓冲区的内容未初始化为零。
您分配 size + 1
字节的缓冲区,然后从文件中读取 size
字节。缓冲区中的最后一个字节,你因为 + 1
而得到的字节?你从来没有给它写过任何东西。该字符串缺少空终止符,fread
不会为您这样做。
您必须自己添加空终止符,否则 puts
将在尝试打印它时遇到未定义的行为:
buffer[result] = '[=10=]';
除了 好的答案,OP 的代码还有其他弱点。
不检查是否打开成功
FILE* file = fopen(filename, "rb"); // error check
// add
if (file == NULL) {
fprintf(stderr, "Unable t open file <%s>\n", filename);
return EXIT_FAILURE;
}
无错误检查,不需要L
//fseek(file, 0L, SEEK_END); // error check this too
if (fseek(file, 0, SEEK_END)) {
// add error message here
return EXIT_FAILURE;
}
类型错误,未进行错误检查
// unsigned int size = ftell(file);
// ftell returns a long
long size = ftell(file);
if (size == -1) {
// add error message here
return EXIT_FAILURE;
}
rewind(file); // this is OK
无大小错误检查、概念错误乘法、无分配检查
// char* buffer = malloc(sizeof(char) * size + 1); // error check
if (size < 0 || size >= SIZE_MAX) {
fprintf(stderr, "Size %ld was to large to convert to size_t\n", size);
return EXIT_FAILURE;
}
// Even though it makes no arithmetic difference as sizeof(char) is 1,
// multiply the size of the object by the _sum_
// char* buffer = malloc(sizeof(char) * (size + 1));
// Even better as, size by the referenced object, not type
char* buffer = malloc(sizeof buffer[0] * (size + 1));
// Add allocation check
if (buffer == NULL) {
// add error message here
return EXIT_FAILURE;
}
使用正确的return类型,添加结果检查
// unsigned int result = fread(buffer, sizeof(char), size, file);
// fread returns a size_t
// size_t result = fread(buffer, sizeof(char), size, file);
// Even better as
size_t result = fread(buffer, sizeof buffer[0], size, file);
if (result != size) {
// add error message here
return EXIT_FAILURE;
}
fclose(file); // OK
我们终于解决了 OP 最紧迫的问题
代码尝试使用 puts(buffer)
打印,它期望 buffer
是一个 字符串 。字符数组 buffer[]
不一定是字符串,因为它可能缺少 空字符 .
多种解决方案:追加一个空字符.
// Make certain `buffer[]` contains a final '[=15=]'.
buffer[result] = '[=15=]';
// then print
puts(buffer);
或使用 fwrite()
打印,不需要 buffer[]
作为 字符串。
fwrite(buffer, sizeof buffer[0], result, stdout);
更深
剩余问题:
使用 "rb"
读取数据并使用 stdout
写入(使用 puts()
或 fwrite()
。这会在某些平台上产生问题,如写入在 text_mode(stdout
典型默认模式)中,二进制数据 "\r\n"
可能打印为 "\r\r\n"
。解决方法:重新打开stdout
二进制模式。
读取的数据可能包含 空字符,在这种情况下,puts()
将在第一个 '[=32=]'
处提前停止 - 而不是附加的一。 fwrite()
避免了这个问题。
文件大小可能超过 LONG_MAX
或 SIZE_MAX
或超过 malloc()
可用内存。我们需要另一种方式来确定文件大小,并进行不同的分配以应对如此巨大的文件。
在不常见的系统上,fseek(), ftell()
技巧不起作用。最好的解决方案是不需要将整个文件读入一个 array/string,而是读入多个块。将详细信息留到另一天。
我在以下函数中遇到了一些奇怪的行为:
char* read_file(const char* filename) {
FILE* file = fopen(filename, "rb"); // error check
fseek(file, 0L, SEEK_END); // error check this too
unsigned int size = ftell(file); // error check
rewind(file);
// I know a char is a single byte, but just for readibility's sake:
char* buffer = malloc(sizeof(char) * size + 1); // error check
unsigned int result = fread(buffer, sizeof(char), size, file); // more error check
fclose(file);
// puts(buffer); // see the rest of the question, this is line 16
return buffer;
}
如果第 16 行保持注释,我的程序中出现未定义的行为,即返回的字符串不是我预期的,即发生错误。通过错误检查,我避免了这个问题,我得到 result
等于 size
,所以 fread 成功读取了文件。现在,如果我取消注释第 16 行并打印结果,一切正常并且不会发生未定义的行为。为什么会发生这种情况?
malloc
分配未初始化的存储,这意味着返回缓冲区的内容未初始化为零。
您分配 size + 1
字节的缓冲区,然后从文件中读取 size
字节。缓冲区中的最后一个字节,你因为 + 1
而得到的字节?你从来没有给它写过任何东西。该字符串缺少空终止符,fread
不会为您这样做。
您必须自己添加空终止符,否则 puts
将在尝试打印它时遇到未定义的行为:
buffer[result] = '[=10=]';
除了
不检查是否打开成功
FILE* file = fopen(filename, "rb"); // error check
// add
if (file == NULL) {
fprintf(stderr, "Unable t open file <%s>\n", filename);
return EXIT_FAILURE;
}
无错误检查,不需要L
//fseek(file, 0L, SEEK_END); // error check this too
if (fseek(file, 0, SEEK_END)) {
// add error message here
return EXIT_FAILURE;
}
类型错误,未进行错误检查
// unsigned int size = ftell(file);
// ftell returns a long
long size = ftell(file);
if (size == -1) {
// add error message here
return EXIT_FAILURE;
}
rewind(file); // this is OK
无大小错误检查、概念错误乘法、无分配检查
// char* buffer = malloc(sizeof(char) * size + 1); // error check
if (size < 0 || size >= SIZE_MAX) {
fprintf(stderr, "Size %ld was to large to convert to size_t\n", size);
return EXIT_FAILURE;
}
// Even though it makes no arithmetic difference as sizeof(char) is 1,
// multiply the size of the object by the _sum_
// char* buffer = malloc(sizeof(char) * (size + 1));
// Even better as, size by the referenced object, not type
char* buffer = malloc(sizeof buffer[0] * (size + 1));
// Add allocation check
if (buffer == NULL) {
// add error message here
return EXIT_FAILURE;
}
使用正确的return类型,添加结果检查
// unsigned int result = fread(buffer, sizeof(char), size, file);
// fread returns a size_t
// size_t result = fread(buffer, sizeof(char), size, file);
// Even better as
size_t result = fread(buffer, sizeof buffer[0], size, file);
if (result != size) {
// add error message here
return EXIT_FAILURE;
}
fclose(file); // OK
我们终于解决了 OP 最紧迫的问题
代码尝试使用 puts(buffer)
打印,它期望 buffer
是一个 字符串 。字符数组 buffer[]
不一定是字符串,因为它可能缺少 空字符 .
多种解决方案:追加一个空字符.
// Make certain `buffer[]` contains a final '[=15=]'.
buffer[result] = '[=15=]';
// then print
puts(buffer);
或使用 fwrite()
打印,不需要 buffer[]
作为 字符串。
fwrite(buffer, sizeof buffer[0], result, stdout);
更深
剩余问题:
使用
"rb"
读取数据并使用stdout
写入(使用puts()
或fwrite()
。这会在某些平台上产生问题,如写入在 text_mode(stdout
典型默认模式)中,二进制数据"\r\n"
可能打印为"\r\r\n"
。解决方法:重新打开stdout
二进制模式。读取的数据可能包含 空字符,在这种情况下,
puts()
将在第一个'[=32=]'
处提前停止 - 而不是附加的一。fwrite()
避免了这个问题。文件大小可能超过
LONG_MAX
或SIZE_MAX
或超过malloc()
可用内存。我们需要另一种方式来确定文件大小,并进行不同的分配以应对如此巨大的文件。在不常见的系统上,
fseek(), ftell()
技巧不起作用。最好的解决方案是不需要将整个文件读入一个 array/string,而是读入多个块。将详细信息留到另一天。