为什么我的简单C程序向stdout显示垃圾?
考虑以下简单的C程序,该程序将文件读入缓冲区并将该缓冲区显示到控制台:
#include main() { FILE *file; char *buffer; unsigned long fileLen; //Open file file = fopen("HelloWorld.txt", "rb"); if (!file) { fprintf(stderr, "Unable to open file %s", "HelloWorld.txt"); return; } //Get file length fseek(file, 0, SEEK_END); fileLen=ftell(file); fseek(file, 0, SEEK_SET); //Allocate memory buffer=(char *)malloc(fileLen+1); if (!buffer) { fprintf(stderr, "Memory error!"); fclose(file); return; } //Read file contents into buffer fread(buffer, fileLen, 1, file); //Send buffer contents to stdout printf("%s\n",buffer); fclose(file); }
它将读取的文件只包含:
你好,世界!
输出是:
Hello World!²²²²
已经有一段时间了,因为我在C / C ++中做了很多重要事情,但通常我会假设缓冲区的分配大于必要的,但事实并非如此。
fileLen最终为12,这是准确的。
我现在在想,我必须只是显示错误的缓冲区,但我不确定我做错了什么。
谁能让我知道我做错了什么?
你需要NUL终止你的字符串。 加
buffer[fileLen] = 0;
在打印之前。
JesperE的方法可行,但您可能有兴趣知道有另一种方法可以解决这个问题。
通过将printf
的长度作为字符串字段的精度提供,即使没有NUL终止符,也可以始终打印已知长度的字符串:
printf("%.*s\n", fileLen, buffer);
这允许您在不修改缓冲区的情况下打印字符串。
关于你的例子中的nul-termination问题,JesperE是正确的,我只想补充说,如果你正在处理文本文件,最好使用fgets()或类似的东西,因为这将适当地处理不同平台上的换行序列,并且总是nul-为你终止字符串。 如果您真的使用二进制数据,那么您不希望使用printf()输出数据,因为printf函数需要字符串,数据中的nul字节将导致输出截断。
您通过寻找文件末尾然后使用ftell()
来确定文件大小的方法是错误的:
- 如果它是一个文本文件,在
fopen()
调用的第二个参数中没有"b"
fopen()
,那么ftell()
可能不会告诉你可以从文件中读取的字符数。 例如,windows使用两个字节作为行尾,但读取时,它是一个char
。 实际上,在文本模式下打开的流的ftell()
返回值仅在调用fseek()
有用,而不是用于确定文件大小。 - 如果是二进制文件,在
fopen()
的第二个参数中用"b"
fopen()
,那么C标准就是这样说的:
将文件位置指示符设置为文件结尾,与
fseek(file, 0, SEEK_END)
,具有二进制流的未定义行为(因为可能是尾随空字符)或具有不依赖于状态的编码的任何流确定在初始class次状态结束。
所以,你正在做的不一定是在标准C中工作。最好的办法是使用fread()
来读取,如果你碰巧需要更多内存,请使用realloc()
。 您的系统可能提供mmap()
,或者可以保证将文件位置指示符设置为二进制流的文件结尾 – 但依赖它们是不可移植的。
另请参阅此C-FAQ: 文本和二进制I / O之间的区别是什么? 。
您可以使用calloc
而不是malloc
来分配已初始化的内存。 calloc
需要额外的参数。 它对分配数组很有用; calloc
的第一个参数表示要为其分配内存的数组中的元素数,第二个参数是每个元素的大小。 由于char
的大小始终为1,因此我们可以将1
作为第二个参数传递:
buffer = calloc (fileLen + 1, 1);
在C中,不需要转换malloc
或calloc
的返回值。 以上将确保即使由于某种原因提前结束文件的读取,该字符串也将被终止。 calloc
确实需要比malloc
更长的时间,因为它必须将所有内存归零,然后再将其提供给你。