使用系统调用的时候用文件描述符(file descriptor,简称fd)的时候比较多,但是操作比较原始。C库函数在I/O上提供了一些方便的包装(比如格式化I/O、重定向),但是对细节的控制不够。

如果过度依赖其中的一种只会徒增麻烦,所以知道两者的转换是很有必要的。FILE*是对fd的封装

当然,有人会说知道文件路径的话重新打开就是了,但是这会产生竞争条件(Race Conditions),首先重新打开文件,相当于是2个fd指向同一文件,然后如果在打开的期间文件被删除了又被新建了一个同名文件,2个fd指向的便是不同的文件。

glibc库提供了两个转换函数fdopen(3)和fileno(3),都是<stdio.h>中的

FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);

PS:为了节省篇幅,还是继续忽略返回值的检查。

来看看测试吧,是不是我们想的那样。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> int main()
{
const char* filename = "new.txt";
int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); FILE* fp = fdopen(fd, "w+");
int fd2 = fileno(fp); printf("fd=%d | fd2=%d\n", fd, fd2); fclose(fp);
close(fd);
return ;
}
$ gcc test.c
$ ./a.out
fd= | fd2=

参考fileno手册:The function fileno() examines the argument stream and returns its integer descriptor.

FILE是对fd的封装,fileno()是直接取得被封装的fd,因此并未创建新的fd指向该文件。

参考fdopen手册:

The fdopen() function associates a stream with the existing file descriptor, fd. The mode of
the stream (one of the values "r", "r+", "w", "w+", "a", "a+") must be compatible with the
mode of the file descriptor.

fdopen()是讲流(FILE对象)与已存在的文件描述符fd进行关联,因此也是未创建新的fd。值得注意的是,FILE指针的模式(mode)必须与文件描述符的模式兼容。

关于mode参数先搁置会儿,目前我们知道的是,使用fileno和fdopen进行转换,都是在原有的fd上进行操作,并未产生新的fd。那么,再次审视刚才的代码,是否发现了问题?

我们来检查下close(fd)的返回值,把close(fd)改成下列代码

    if (- == close(fd)) {
perror("close");
exit();
}
$ gcc test.c
$ ./a.out
close: Bad file descriptor

没错,fclose在关闭文件指针的时候,内部其实也关闭了文件描述符(否则资源就泄露了),既然这里fp内部的文件描述符和fd是同一个,当fp被关闭时,fd也被关闭了,再次关闭fd就会出现“损坏的文件描述符”错误。

OK,现在回顾下fopen的第2个参数,又r/r+/w/w+/a/a+一共6种设置(windows平台的rb/rb+/wb/wb+暂且不谈),对比Linux手册我将对应的open设置列出来

依然是进行测试,修改fd_mode和fp_mode,看看实验结果

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> const int security = S_IRUSR | S_IWUSR;
const int fd_mode = O_RDWR | O_CREAT | O_TRUNC;
const char* fp_mode = "r"; int main()
{
int fd = open("new.txt", fd_mode, security);
FILE* fp = fdopen(fd, fp_mode);
if (fp == NULL) {
perror("fdopen");
exit();
} close(fd);
return ;
}

在fd_mode等价于"w+"时,fp_mode的6种设置(r/r+/w/w+/a/a+)均返回非空指针。

在fd_mode等价于"w"时,fp_mode6种设置只有"a"和"w"返回非空指针。

继续尝试"r"/"r+"/"a"/"a+"的设置,可以发现所谓“兼容”只与读写权限有关,O_RDWR兼容O_RDONLY和O_WRONLY,而后两者则只与自身兼容。

有意思的是O_APPEND(在末尾添加)和O_TRUNC(截断文件从头添加)也兼容。

The file position indicator of the new stream is set to that
belonging to fd, and the error and end-of-file indicators are cleared. Modes "w" or "w+" do
not cause truncation of the file. The file descriptor is not dup'ed, and will be closed when
the stream created by fdopen() is closed.

继续查看fdopen的手册内容,可以看到"w"和"w+"在这里不会导致文件截断。

后一句也印证了我们前面的实验结果:文件描述符不会被复制,文件指针被关闭时文件描述符也会被关闭。

PS:其实fdopen的手册上还有最后一句:The result of applying fdopen() to a shared memory object is undefined.

将fdopen用于共享内存对象的结果是未定义的。

【详解】Linux的文件描述符fd与文件指针FILE*互相转换的更多相关文章

  1. Linux中文件描述符fd和文件指针flip的理解

    转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通 ...

  2. [转载] linux中文件描述符fd和文件指针flip的理解

    转载自http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通 ...

  3. 文件描述符fd、文件指针fp和vfork()

    1. fd:在形式上是一个非负整数.实际上他是一个索引值.指向kernal为每一个进程所维护的该进程打开文件的记录表. 当程序打开一个文件或者创建一个新文件的时候kernal向进程返回一个文件描述符. ...

  4. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  5. 进程间传递文件描述符fd

    众所周知,子进程会继承父进程已经打开的文件描述符fd,但是fork之后的是不会被继承的,这个时候是否无能无力了?答应是NO.Linux提供了一个系统调用sendmsg,借助它,可以实现进程间传递文件描 ...

  6. 文件描述符fd,struct files_struct

    程序可以理解为硬盘上的普通二进制文件:进程是加载到内存中的二进制文件,除了加载到内存中的二进制文件外,还附有所有对于该二进制文件描述信息的结构体,描述该进程的结构体叫PCB(进程控制块),在这就不在讨 ...

  7. 文件描述符FD的含义/文件句柄

    使用sudo lsof -nP -iTCP -sTCP:LISTEN查看占用端口的程序;因为 lsof 需要访问核心内存和各种文件,所以必须以 root 用户的身份运行它才能够充分地发挥其功能 概念 ...

  8. 文件描述符fd

    java 后台运行程序命令 nohup java -jar babyshark-0.0.1-SNAPSHOT.jar > log.file 2>&1 & 命令解释:后台启动 ...

  9. Linux文件描述符与打开文件之间的区别(转载)

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/38965239   1. 概述     在Linux系统中一切皆可以看成是文件,文件又可分为: ...

随机推荐

  1. linux体系结构与内核结构图解

    1.当被问到Linux体系结构(就是Linux系统是怎么构成的)时,我们可以参照下图这么回答:从大的方面讲,Linux体系结构可以分为两块: (1)用户空间:用户空间中又包含了,用户的应用程序,C库 ...

  2. ngular6开发不完全笔记(三)-- 报错指南

    router Uncaught Error: Template parse errors: 'router-outlet' is not a known element: If 'router-out ...

  3. Ajax请求304问题

    ajax默认是开启缓存的,所以get请求如果路径一样,会先找缓存,如果缓存存在就用缓存. 解决方案: 1.在修改url为动态变化的,如url后面加一个&timestamp=Date.parse ...

  4. Xcode6中手动添加Precompile Prefix Header

    Xcode5中创建一个工程的时候,系统会自动创建一个以以工程名为名字的pch(Precompile Prefix Header)文件,开发的过程中可以将广泛使用的头文件以及宏包含在该文件下,编译器就会 ...

  5. 在vue中使用scss的配置

    1.创建一个基于 webpack 模板的新项目 $ vue init webpack myvue 2.在当前目录下,安装依赖 $ cd myvue$ npm install 3.安装sass的依赖包 ...

  6. phython

    转载 http://www.cnblogs.com/chenny7/p/4062693.html

  7. 051——VUE中自定义指令:directive

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. java Web 工程servlet中@WebServlet("/HelloServlet") 是怎么工作的

    编写好Servlet之后,接下来要告诉Web容器有关于这个Servlet的一些信息.在Servlet 3.0中,可以使用标注(Annotation)来告知容器哪些Servlet会提供服务以及额外信息. ...

  9. PHP7.2.12-Configuration-Option

    # PHP7.2.12 <pre style="background-color: rgb(255,250,233);"> `configure' configures ...

  10. vue.js 源代码学习笔记 ----- fillter-parse.js

    /* @flow */ export function parseFilters (exp: string): string { let inSingle = false let inDouble = ...