C fopen与open的区别

277

除了语法上的原因,你还想使用它的任何理由吗?

FILE *fdopen(int fd, const char *mode);
或者
FILE *fopen(const char *path, const char *mode);
代替
int open(const char *pathname, int flags, mode_t mode);

在Linux环境下使用C语言时需要注意什么?


你是指 fdopenopen 还是 fopenopen - user7116
12
fopen是标准C库的一部分,而open则不是。编写可移植代码时应使用fopen - Aziz
1
是的,我指的是fopen。我刚刚更新了它,但我认为同样的原则适用。 - LJM
9
@Aziz,“open”是一个POSIX函数。 - dreamlax
fopen也使用open函数:FILE* fopen(const char* file, const char* mode) {... int fd = open(file, mode_flags, DEFFILEMODE);...FILE* fp = __fopen(fd, flags);...return fp;} 这里的__fopen函数使用__sfp()来创建一个FILE对象,并将fd放入其中。顺便问一下,__sfp()在哪里? - jw_
显示剩余4条评论
12个回答

294

其中,fdopen与其他函数不同。如果您先调用了open,然后想要一个FILE *时,可以使用fdopen。如果有选择的话,这样做没有意义,而是直接调用fopen。可能有一些情况下,fopen无法完全满足您的需求,但它们超出了本问题的范围。

因此,我们假装fdopen不存在,并继续提出问题。

使用fopen而不是open有四个主要原因。

  1. fopen 提供了缓冲IO,可能比你使用 open 更快。
  2. fopen 如果文件没有以二进制模式打开,会进行换行符转换,这在将来将程序移植到非Unix环境时非常有帮助(尽管世界似乎正在趋于仅支持LF的环境,除了IETF的文本协议如SMTP和HTTP等)。
  3. FILE * 让你能够使用 fscanf 和其他stdio函数。
  4. 你的代码将来可能需要移植到某些只支持ANSI C而不支持 open 函数的平台。

在我看来(并根据我的经验),换行符转换更多时候会妨碍你而不是帮助你,而 fscanf 的解析功能很弱,你最终会选择放弃它,转而使用更实用的方法。

大多数支持C语言的平台都有一个 open 函数。

那就剩下缓冲问题了。在主要顺序读写文件的地方,缓冲支持确实非常有帮助,可以大幅提高速度。但它可能导致一些有趣的问题,即数据没有按预期出现在文件中。你必须记住在适当的时候使用fclose或fflush。
如果你正在进行查找(也称为fsetpos或fseek,后者稍微难以以符合标准的方式使用),缓冲的有用性会迅速降低。
当然,我的偏见是我经常与套接字一起工作,在那里你真的希望进行非阻塞IO(而FILE *在任何合理的方式上都无法支持)而且通常有复杂的解析要求,这确实影响了我的看法。

4
我不会质疑你的经历,但我很想听听你详细阐述一下。你认为内置缓冲对哪些应用程序造成了影响?具体问题是什么? - Emil H
11
当缓冲妨碍操作时需要澄清。当您使用“seek”时,会发生这种情况。随后使用任何命令(如“fgets”,“fgetc”,“fscanf”,“fread”)进行读取,都将始终读取缓冲区的整个大小(4K、8K或您设置的其他大小)。通过使用直接I/O,可以避免这种情况。在这种情况下,最好使用“pread”而不是“seek/read”对(1次系统调用而不是2次)。 - Patrick Schlüter
3
处理中断的read()write()调用是使用libc函数族的方便第五个原因。 - nccc
4
@m-ric: 嗯,这是一个有点无关的问题,但是是的。所有支持 ioctl 的平台也支持 fileno 调用,它接受一个 FILE * 并返回一个数字,该数字可以用于 ioctl 调用。不过要小心。与 ioctl 更改底层文件描述符相关的 FILE * 相关调用可能会产生意外的交互作用。 - Omnifarious
2
我拥有一个地图渲染库(CartoType),它具有自己的缓冲功能。我尝试从高级到低级io(从fopen到open等)进行实验,并在之前和之后对性能进行了基准测试。低级io更好,但只有3%的提升。我还没有决定3%的改进是否值得。可能是的,因为我可以封装平台相关的代码。 - Graham Asher
显示剩余8条评论

76

open() 是一种底层的操作系统调用。 fdopen() 可以将操作系统级别的文件描述符转换为C语言中更高级别的FILE抽象。 fopen() 在后台调用了open(),并直接给你一个FILE指针。

使用 FILE 对象而不是原始文件描述符有几个优点,包括更容易使用以及其他技术优势,例如内置缓冲。特别是缓冲通常会带来可观的性能优势。


6
使用带缓冲的'f...'版本的open有什么缺点吗? - LJM
10
是的,当您已经缓冲数据时,额外的缓冲区会增加不必要的复制和内存开销。 - Michael Aaron Safyan
11
实际上,还有其他缺点。例如,fopen() 在打开文件时无法提供与创建权限、共享模式等相关的同样高级别的控制。通常,open() 和其变种会提供更多的控制,接近于操作系统实际提供的控制水平。 - Matt Joiner
5
有时候也会出现极端情况,你需要使用 mmap 将文件映射到内存中,并进行常规 I/O 操作(尽管听起来难以置信,但我们在项目中确实这样做了,而且出于非常好的原因),这时缓冲会成为阻碍。 - Patrick Schlüter
您可能还想使用其他系统函数,例如使用open()通过readahead()将文件预加载到页面缓存中。 我猜经验法则是“除非您绝对需要open(),否则请使用fopen()”,open()实际上可以让您做一些花哨的事情(设置/不设置O_ATIME等)。 - Tomas Pruzina

66

fopenopen在C语言中的区别

  1. fopen是一个库函数,而open是一个系统调用

  2. fopen提供缓冲IO,相比之下open非缓冲IO,可能更快。

  3. fopen可移植的,而open不是可移植的open是环境特定的)。

  4. fopen返回一个指向FILE结构体(FILE *的指针,而open返回一个标识文件的整数。

  5. FILE *使您能够使用fscanf和其他stdio.h函数。


15
“open”是POSIX标准,因此非常易于移植。 - osvein
fopen() 不是 async-signal-safe(缓冲io都不是),因此它不应该在任何信号处理程序中使用 - 或者在可能被调用的函数中使用,并且如果在使用异步线程的函数中使用,则应该小心。使用 open() 会增加复杂性并且缺少缓冲io... - Simon Sobisch
@osvein 现在已经不太具有可移植性了。在Linux上运行良好,但在Windows上已经被弃用。现在应该使用_open、_close。这只是一个小问题。我想Linux和Windows所需的头文件也不同。我认为如果没有一些“#if OS_LINUX”行,编写可移植代码将会很困难。 - Jiminion
@Jiminion 在Linux上运行良好,但现在在Windows上已被弃用。 微软也相当可笑地“弃用”了fopen()还有strcpy()。以及C语言中可能一半以上所需的函数。现在应该使用_open、_close。 不,你不是“应该使用”。“微软希望你这样做是因为它不具备可移植性”可能更接近实际情况 - Andrew Henle
6
"open"是一个系统调用。唉,我不知道为什么存在这种毫无意义的人工分界。在几乎所有现有的POSIX系统上,“open()”都是像“fopen()”一样的库函数。 "open()"库函数通常是围绕"openat"系统调用实现的。即使在没有"openat()"系统调用之前,“open()"也是一个库函数,它包装了"open()"系统调用。"fopen()"只是一个更大的库函数包装器,用于使用"open()"库函数使用相同的系统调用。" - Andrew Henle
@osvein - 安德鲁是对的。这个区别很重要要记住。开放的libc函数是一个封装系统调用的函数。在较新版本的libc中,它实际上封装了openat系统调用。 - Omnifarious

21
除非你是那0.1%的应用程序之一,使用open确实可以提高性能,否则没有理由不使用fopen。至于fdopen,如果你不涉及文件描述符,就不需要使用这个调用。
坚持使用fopen及其方法族(如fwritefreadfprintf等),你会感到非常满意。同样重要的是,其他程序员也会对你的代码感到满意。

我仍然使用 open,因为我不喜欢 const char *mode 而更喜欢 int mode - user16217248

12

如果你有一个FILE *,你可以使用像fscanffprintffgets等函数。如果你只有文件描述符,你将拥有受限的(但可能更快)输入输出例程:readwrite等。


9

open()是一个系统调用,仅适用于基于Unix的系统,并返回文件描述符。您可以使用另一个系统调用write()向文件描述符写入内容。
fopen()是一个ANSI C函数调用,返回一个文件指针,可在其他操作系统中使用。我们可以使用fprintf向文件指针写入内容。

在Unix中:
您可以使用以下方法从文件描述符获取文件指针:

fP = fdopen(fD, "a");

您可以使用以下方法从文件指针中获取文件描述符:
fD = fileno (fP);

8

使用open、read、write等函数需要担心信号中断的问题。

如果调用被信号处理程序中断,那么这些函数将返回-1并设置errno为EINTR。

因此,正确的关闭文件的方法是

while (retval = close(fd), retval == -1 && ernno == EINTR) ;

5
对于“close”这个操作,这取决于操作系统。在Linux、AIX和其他一些操作系统上,在循环中执行该操作是不正确的。 - strcat
1
另外,使用 read 和 write 也会遇到同样的问题,即它们在完全处理输入/输出之前可能会被信号中断,程序员必须处理这种情况,而 fread 和 fwrite 则可以很好地处理信号中断。 - Marcelo
我已经看到太多次这种情况,但从未有一个好的理由说明为什么这会成为问题。也许您可以提供一个实际上需要不在信号处理程序中使用“SA_RESTART”而是使系统调用返回“EINTR”的示例? - user11877195

6

我在我的应用程序中从fopen()改为使用open(),因为fopen()每次运行fgetc时会导致双重读取。这种双重读取会干扰我想要完成的任务。相比之下,open()似乎只是按照你的要求执行操作。


3

open()函数会在每个fopen()系列函数的结尾处被调用。 open() 是一个系统调用,而 fopen() 则是由库作为包装函数提供给用户使用的。


3

具体要看打开文件需要哪些标志。为了写入和读取(以及可移植性),应该使用f*,正如上面所述。

但是,如果基本上想要指定超出标准标志的内容(例如rw和追加标志),则必须使用特定于平台的API(例如POSIX open)或抽象这些细节的库。C标准没有任何这样的标志。

例如,您可能只想打开一个文件,如果存在的话。如果不指定创建标志,则文件必须存在。如果将独占添加到创建中,则仅在文件不存在时才会创建该文件。还有更多其他标志。

例如,在Linux系统上,通过sysfs公开了LED接口。它通过文件公开了LED的亮度。将数字作为字符串从0-255进行写入或读取。当然,您不希望创建该文件,并且仅在存在时才进行写入。现在很酷的事情是:使用标准调用使用fdopen读/写此文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接