我的理解是,fopen()
和open()
都可以用于打开文件。 open()
返回一个文件描述符。但是它们在获取要写入或读取的文件方面应该是等效的。定义文件描述符的目的是什么?从维基页面上不清楚。
我的理解是,fopen()
和open()
都可以用于打开文件。 open()
返回一个文件描述符。但是它们在获取要写入或读取的文件方面应该是等效的。定义文件描述符的目的是什么?从维基页面上不清楚。
fopen
返回一个 FILE *
,它是文件描述符的包装器(我将忽略“规范不需要这样做”的方面,因为我不知道有实现不这样做)。在高层次上,它看起来像这样:
最初的回答:
fopen
函数返回一个 FILE *
类型的值,它是对文件描述符的封装(虽然规范并不要求这么做,但实际上所有的实现都这么做了)。简单来说,它的作用就是把文件描述符进行包装,使其更易于操作。
application --FILE *--> libc --file descriptor--> kernel
Shell脚本直接操作文件描述符,主要是因为它们正在执行其他程序,而您无法修改其他程序的FILE *
对象。但是,在启动时(即在fork
和exec
之间),可以使用dup
系统调用来修改其他程序的文件描述符。例如:
/bin/cat > foo.txt
这行命令告诉shell执行/bin/cat
程序,但首先将标准输出(文件描述符#1)重定向到它打开的文件中。伪代码实现如下:
(最初的回答)
if (fork() == 0) {
int fd = open("foo.txt");
dup2(fd, 1);
exec("/bin/cat");
}
FILE *
的方法是调用freopen
,但是与文件描述符不同,在使用exec
时不会保留这个方法。但为什么我们需要FILE *
呢?因为它是围绕文件描述符的一种封装,其中一个主要的好处是拥有预读缓存。例如,考虑fgets
。它最终会在与传入的FILE *
相关联的文件描述符上调用read
系统调用。但是它怎么知道应该读多少呢?内核没有选项可以说“给我一行”(除了行缓冲的ttys)。如果你在第一个read
中读取超过一行,下次调用fgets
时你可能只会得到下一行的一部分,因为内核已经在前一个read
系统调用中给出了第一部分。另一个选择是逐个字符地调用read
,这对性能来说非常糟糕。那么libc做了什么呢?它一次读取一堆字符,然后将额外的字符存储在FILE *
对象的内部缓冲区中。下次调用fgets
时,它可以使用内部缓冲区。这个缓冲区也与fread
等函数共享,因此你可以交替调用fgets
和fread
而不会丢失数据。open()
是一个底层的 POSIX 函数,用于打开文件。它返回一个独特的整数来标识和启用对已打开文件的访问。这个整数是一个“文件描述符”。fopen()
是一个高级、可移植的 C 标准库函数,用于打开文件。fopen()
可能会调用不可移植的 open()
,但这是一个实现细节。fopen()
。man 2 read
。POSIX 的 read()
函数通过由 open()
返回的文件描述符读取数据。open()
将在POSIX可移植的范围内可移植。但是,从被要求提供每个标准托管C安装的库的角度来看,它并不具备可移植性。这回答了你的问题吗? - thbfread()
和fwrite()
函数(使用从fopen()
获取的FILE *
)进行二进制I/O。此外,如果需要缓冲I/O,则可以自动获得它。您能否更具体地说明为什么您认为基于文件描述符的I/O比流I/O更适合这些任务? - John Bollingeropen()
达到这个目的20年了。鉴于您的建议,我已删除有关二进制和fopen()
的段落。 - thb
FILE
不一定是缓冲的。但这些都没有真正回答所提出的问题。POSIX 可以将与文件描述符相关的大多数内容定义为(指向)FILE
对象。观察到它没有这样做并不能解释为什么它没有这样做。 - John Bollinger