File read using POSIX API's
考虑下面的代码片段,用于将文件内容读入缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #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 ",bytes_read); } //Test to characters read from the file to buffer.The file contains"Hello" while(buff[i]!='\0') { printf("buff[%d]=%d ",i,buff[i]); i++; //buff[5]= -How? } //buff[6]=`\0`-How? close(fd); return 0; } |
代码说明:
- 输入文件包含字符串"Hello"
- 需要将此内容复制到缓冲区中。
-
目标是通过
open 和read POSIX API实现的。 - 读取API使用指向*仲裁大小*的缓冲区的指针来复制数据。
问题:
- 动态分配是必须用于优化缓冲区大小的方法。从输入文件大小关联/派生缓冲区大小的正确过程是什么?
-
我在
read 操作结束时看到,除了字符"Hello"之外,read还复制了一个new line character 和一个NULL 字符。请详细说明这种阅读行为。
样本输出
bytes_read=6
buff[0]=H
buff[1]=e
buff[2]=l
buff[3]=l
buff[4]=o
buff[5]=
BLOCKQUOTE>
PS:输入文件是用户创建的文件,不是由程序创建的(使用
write API)。在这里提一下,如果它有任何区别。
由于您想要读取整个文件,最好的方法是使缓冲区与文件大小一样大。在你去的时候调整缓冲区是没有意义的。这只是没有充分理由伤害表现。
您可以通过多种方式获取文件大小。快速而肮脏的方式是
lseek() 到文件的末尾:
1
2
3
4
5
6 // 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() 获取信息:
1
2
3
4 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);要获得所有需要的类型和函数原型,请确保包含所需的标头:
1
2 #include <sys/stat.h> // For fstat()
#include <unistd.h> // For lseek()请注意,
read() 不会自动使用\0 终止数据。您需要手动执行此操作,这就是我们为缓冲区分配额外字符(大小+ 1)的原因。在你的案例中已经存在\0 字符的原因是纯随机机会。当然,由于
buf 现在是一个动态分配的数组,所以当你不再需要时,不要忘记再次释放它:
1但请注意,分配一个与您想要读入的文件一样大的缓冲区可能很危险。想象一下,如果(错误或故意,无所谓)文件是几GB大。对于这样的情况,最好允许最大允许尺寸。但是,如果您不想要任何此类限制,则应切换到另一种从文件中读取的方法:
mmap() 。使用mmap() ,您可以将文件的某些部分映射到内存。这样,文件的大小并不重要,因为您一次只能处理部分文件,从而控制内存使用。
1,你可以用stat(filename,&amp; stat)获取文件大小,但是定义缓冲区到页面大小就好了
2,首先,在"Hello"之后没有NULL字符,在你的代码执行之前你所分配的堆栈区域必须是0,这请参考APUE第7.6章。 实际上,在使用它之前必须初始化局部变量。
我尝试使用vim,emacs和echo -n Hello> file-to-buff.txt生成文本文件,只有vim自动添加换行符
您可以考虑动态分配缓冲区,方法是先使用
malloc 创建一个固定大小的缓冲区,然后在填充时加倍(用realloc )大小。这将有一个很好的时间复杂性和空间权衡。此刻你反复读入相同的缓冲区。每次读取后应增加缓冲区中的点,否则将使用文件的下一部分覆盖缓冲区内容。
您提供的代码为缓冲区分配50个字节,但是将大小传递4096作为
read 。这可能导致超过50个字节的任何文件的缓冲区溢出。至于' n'和' 0'。换行符可能在文件中,' 0'就在缓冲区中。缓冲区在代码中的堆栈上分配,如果堆栈的那个部分尚未使用,它可能包含零,在程序加载时由操作系统放置。
操作系统不会尝试终止从文件读取的数据,它可能是二进制数据或它不理解的字符集。如果需要,终止字符串取决于您。
其他几点更多的是风格问题:
- 您可以考虑使用
for (i = 0; buff[i]; ++i) 循环而不是一段时间来结束打印输出。这样,如果有人混淆索引变量i ,你将不会受到影响。- 您可以在读完文件后提前关闭该文件,以避免文件长时间打开(如果发生某种错误,可能会忘记关闭它)。
对于第二个问题,
read 不会自动添加字符'\0' 。
如果您认为您的文件是文本文件,则必须在调用read 后添加'\0' ,以指示字符串的结尾。在C中,字符串的结尾由此字符表示。如果
read 设置4个字符,printf 将读取这4个字符,并将测试第5个字符:如果它不是'\0' ,它将继续打印直到下一个'\0' 。
它也是缓冲区溢出的来源对于
' ,它可能在输入文件中。
'