0%

fd_open_write 是一组奇妙的组合

  • 今天帮同学调bug的时候,遇到一个很神奇的问题
  • 觉得特别有意思,在此记录下来

我们最近在学习Linux文件编程,需要用到open()函数、write()函数等等; 当fd、open()、write()这仨东西凑到一起,一不留神就会有神奇效果

1 预备知识

要想看懂这个奇妙的过程,你可能需要先了解一下下面这些函数

1.1 文件描述符

对于Linux而言,所有对设备和文件的操作都使用文件描述符来进行,文件描述符就相当于文件的身份证。文件描述符是一个非负的整数,表示为int类型的对象。它是一个索引值,指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建的一个新文件时,内核就向进程返回一个文件描述符。当需要读写文件时,也需要把文件描述符传递给相应的函数。

文件描述表的前三项对于一般的进程而言是固定的且由系统自动打开。文件描述符0是标准输入文件,对于一般进程来说是键盘;文件描述符1是标准输出文件,一般是显示器;文件描述符2是标准错误输出文件,一般也是输出到屏幕。[1]

1.2 open函数

调用open函数可以打开或创建一个文件,open是进程存取文件数据时,必须首先完成的系统调用。[1]

1
2
3
4
5
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags, .../* mode_t mode */);
// 返回值:成功时返回被打开文件的文件描述符 fd;失败时返回 -1

表1.open函数的参数

参数 含义
pathname 表示要打开(或创建)的文件名或含路径的文件名
flags 表示打开文件的方式,具体方式见下表
mode 当flags为O_CREAT时,该项表示创建文件的权限

表2. flags参数的取值[2]

flags 参数 含义
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 可读可写
O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾
O_CREAT 表示如果指定文件不存在,则创建这个文件
O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值
O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0

表3. mode参数的取值[3]

mode参数 含义
S_IRWXU00700 权限 代表该文件所有者具有可读、可写及可执行的权限
S_IRUSR 或S_IREAD,00400权限 代表该文件所有者具有可读取的权限
S_IWUSR 或S_IWRITE,00200 权限 代表该文件所有者具有可写入的权限
S_IXUSR 或S_IEXEC,00100 权限 代表该文件所有者具有可执行的权限
S_IRWXG 00070权限 代表该文件用户组具有可读、可写及可执行的权限
S_IRGRP 00040 权限 代表该文件用户组具有可读的权限
S_IWGRP 00020权限 代表该文件用户组具有可写入的权限
S_IXGRP 00010 权限 代表该文件用户组具有可执行的权限
S_IRWXO 00007权限 代表其他用户具有可读、可写及可执行的权限
S_IROTH 00004 权限 代表其他用户具有可读的权限
S_IWOTH 00002权限 代表其他用户具有可写入的权限
S_IXOTH 00001 权限 代表其他用户具有可执行的权限

1.3 write函数

write函数是将内存中的数据写入文件中,其格式声明如下:

1
2
3
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
// 返回值:成功时返回写入的字节数(若为0表示没有数据写入);失败时返回 -1

它的功能是将buf所指内存中的count个字节写入文件描述符fd所指的文件中。具体参数含义如下表4:

表4. write函数的参数

参数 含义
fd 表示文件描述符
buf 表示输出缓冲区的地址指针
count 表示要写入的字节数

2 奇妙的组合

相信你已经了解这些基本知识了,下面来看看这个奇妙的组合。 这个奇妙的效果来自于一个程序,是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#define NAME "save.c"
int main()
{
int fd;
char buf[100]="";
if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)
{
perror("fail to open the file\n");
return -1;
}
printf("Please input the Letter\n" );
scanf("%s",buf);
write(fd,buf,strlen(buf));
close(fd);
return 0;
}

上面的代码摘取自我同学的程序,程序的目的是:创建一个新文件,然后从键盘接收用户输入的字符串,将字符串写入该文件。

乍看上去没什么问题,但是测试时会出现以下情况:

测试过程

可以看到,在用户输入数据后,会有一次数据的屏幕回显现象,数据被输出到了屏幕上。

但是,在程序里我们确实没有写任何的输出语句啊。。。😂 怎么会回显到了屏幕上呢?

而且,本来应该写入到文件中的数据,也没有被写入到文件中。。。

3 问题解释

经过仔细的检查,发现了问题所在: 以下是见证奇迹时刻

我一直以为是write()函数出现了问题,没想到其实是if语句的问题 在if语句里,缺少了一个括号(可以仔细看一下)。这样的话,由于C语言赋值符号优先级较低,所以open函数会先与==号结合,导致文件描述符的错误赋值。

在正确打开文件的情况下,if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)这句将fd赋值为0

在打开文件失败的情况下,if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)这句将fd赋值为1

很巧的是,01在文件描述符中,分别表示标准输入流(stdin)和标准输出流(stdout

文件 文件描述符
标准输入 0
标准输出 1
标准错误 2

因此,在正确打开文件的情况下,fd被赋值为0。之后调用write函数时,就把buf里的数据输入到了stdin,在输入缓冲区被刷新时,数据被输出到了屏幕,因此就会有数据的回显效果。

同理,在错误打开文件的情况下,write函数也会将数据错误地输出到stdout

若想获得正确的结果,需要添加括号改变运算顺序,改为下面的语句即可:

1
if((fd=open(NAME,O_CREAT|O_RDWR,0666))==-1)

4 总结

所以说,C语言的优先级和结合律也是非常重要的问题。编程时一定要注意细节,否则会出现意想不到的问题。 哈哈哈,挺好玩的。

5 参考资料

  • [1]. 《Linux C编程从入门到精通》第三章 Linux下的文件编程 - 刘学勇编著 - ISBN 978-7-121-17415-5
  • [2]. 【Linux】open函数的参数和作用 - ArchyLi - CSDN博客
  • [3]. Linux编程下open()函数的用法 - 魏波- CSDN博客
谢谢你请我喝快乐水 😜

欢迎关注我的其它发布渠道