考虑以下用于将文件内容读入缓冲区的代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BLOCK_SIZE 4096
int main()
{
int fd=-1;
ssize_t bytes_read=-1;
int i=0;
char buff[50];
//Arbitary size for the buffer?? How to optimise.
//Dynamic allocation is a choice but what is the
//right way to relate the file size to bufffer size.
fd=open("./file-to-buff.txt",O_RDONLY);
if(-1 == fd)
{
perror("Open Failed");
return 1;
}
while((bytes_read=read(fd,buff,BLOCK_SIZE))>0)
{
printf("bytes_read=%d\n",bytes_read);
}
//Test to characters read from the file to buffer.The file contains "Hello"
while(buff[i]!='\0')
{
printf("buff[%d]=%d\n",i,buff[i]);
i++;
//buff[5]=\n-How?
}
//buff[6]=`\0`-How?
close(fd);
return 0;
}
代码说明:
open
和read
POSIX API实现。问题:
read
操作结束时看到,除了字符“Hello”之外,读取还复制了new line character
和NULL
字符。请详细说明这种阅读行为。示例输出
bytes_read缓存= 6
的buff [0] = H
的buff [1] = E
的buff [2] = 1
的buff [3] = 1
的buff [4] = O
的buff [5] =
PS:输入文件是用户创建的文件,不是由程序创建的(使用write
API)。在这里提一下,如果它有任何区别。
答案 0 :(得分:6)
由于您想要读取整个文件,最好的方法是使缓冲区与文件大小一样大。在你去的时候调整缓冲区是没有意义的。这只会在没有充分理由的情况下伤害表现。
您可以通过多种方式获取文件大小。快速而肮脏的方式是lseek()
到文件的末尾:
// Get size.
off_t size = lseek(fd, 0, SEEK_END); // You should check for an error return in real code
// Seek back to the beginning.
lseek(fd, 0, SEEK_SET);
// Allocate enough to hold the whole contents plus a '\0' char.
char *buff = malloc(size + 1);
另一种方法是使用fstat()
获取信息:
struct stat fileStat;
fstat(fd, &fileStat); // Don't forget to check for an error return in real code
// Allocate enough to hold the whole contents plus a '\0' char.
char *buff = malloc(fileStat.st_size + 1);
要获取所有需要的类型和函数原型,请确保包含所需的标题:
#include <sys/stat.h> // For fstat()
#include <unistd.h> // For lseek()
请注意,read()
不会使用\0
自动终止数据。您需要手动执行此操作,这就是我们为缓冲区分配额外字符(大小+ 1)的原因。在你的案例中已经存在\0
字符的原因是纯随机机会。
当然,由于buf
现在是一个动态分配的数组,所以当你不再需要时,不要忘记再次释放它:
free(buff);
请注意,分配一个与您想要读入的文件一样大的缓冲区可能会很危险。想象一下,如果(错误或故意,无所谓)文件是几GB大。对于这样的情况,最好允许最大允许尺寸。但是,如果您不想要任何此类限制,则应切换到另一种从文件中读取的方法:mmap()
。使用mmap()
,您可以将文件的某些部分映射到内存。这样,文件的大小并不重要,因为您一次只能处理部分文件,从而可以控制内存使用情况。
答案 1 :(得分:3)
1,您可以使用stat(filename,&amp; stat)获取文件大小,但是将页面大小定义为缓冲区
2,首先,在“Hello”之后没有NULL字符,在执行代码之前,你所分配的堆栈区域必须是0,请参考APUE第7.6章。实际上,在使用它之前必须初始化局部变量。
我尝试使用vim,emacs和echo -n生成文本文件Hello&gt; file-to-buff.txt,只有vim自动添加换行符
答案 2 :(得分:2)
您可以考虑动态分配缓冲区,方法是首先使用malloc
创建固定大小的缓冲区,并在填充时加倍(使用realloc
)大小。这将有很好的时间复杂性和空间权衡。
此刻你反复读入同一个缓冲区。您应该在每次读取后增加缓冲区中的点,否则您将使用文件的下一部分覆盖缓冲区内容。
您提供的代码为缓冲区分配50个字节,但是将大小传递4096作为read
。这可能导致超过50个字节的任何文件的缓冲区溢出。
至于'\ n'和'\ 0'。换行符可能在文件中,'\ 0'就在缓冲区中。缓冲区在代码中的堆栈上分配,如果堆栈的那个部分尚未使用,它可能包含零,在程序加载时由操作系统放置在那里。
操作系统不会尝试终止从文件读取的数据,它可能是二进制数据,也可能是不理解的字符集。如果需要,终止字符串取决于你。
其他几点更多的是风格问题:
for (i = 0; buff[i]; ++i)
循环而不是一段时间来结束打印输出。这样,如果有人混淆索引变量i
,你将不会受到影响。答案 3 :(得分:1)
对于第二个问题,read
不会自动添加字符'\0'
。
如果您认为您的文件是文本文件,则必须在调用'\0'
后添加read
,以表明字符串的结尾。
在C中,字符串的结尾由此字符表示。如果read
设置了4个字符,printf
将会读取这4个字符,并会测试第5个字符:如果它不是'\0'
,则会继续打印直到下一个'\0'
。
它也是缓冲区溢出的来源
对于'\n'
,它可能在输入文件中。