第一章:输入输出

  1. 1. 标准I/O
    1. 1.1. fopen(3)
  2. 2. 文件I/O
    1. 2.1. 文件描述符
    2. 2.2. open(2)
    3. 2.3. close(2)
    4. 2.4. read(2)
    5. 2.5. write(2)
    6. 2.6. lseek(2)

主要介绍(1)I/O相关函数;(2)文件描述符

注:标题中显示的函数数字表示该函数在man手册中所在章节(第2章的是系统调用函数,第3章的是标准函数)

I/O是一切实现的基础。

标准I/O

fopen(3)

fopen()函数用于打开一个文件,并返回一个指向FILE结构体的指针,该结构体包含了关于文件的信息,如文件流、读写位置等,以供后续使用高级文件流进行文件的读写操作。它可以用于创建新文件、打开已存在的文件,并指定文件的打开模式(只读、只写、追加等)。函数原型如下:

1
2
3
#include <stdio.h>

FILE *fopen(const char *path, const char *mode);

参数说明:

  • path:要打开的文件路径的字符串,可以是绝对路径或相对路径。
  • mode:一个字符串,用来指定文件打开的模式和选项。模式字符串中的字符代表不同的选项,例如”r”表示只读模式,”w”表示只写模式,”a”表示追加模式等。详细的模式选项见下面的说明。

文件打开模式说明:

  • r“: 只读模式。文件必须存在,否则打开失败。
  • r+“: 读写模式。文件必须存在,可以读取和写入。
  • w“: 只写模式。如果文件不存在,则创建新文件;如果文件存在,则清空文件内容。
  • w+“: 读写模式。如果文件不存在,则创建新文件;如果文件存在,则清空文件内容,可以读取和写入。
  • a“: 追加模式。如果文件不存在,则创建新文件;如果文件存在,则在文件末尾追加写入。
  • a+“: 读写模式。如果文件不存在,则创建新文件;如果文件存在,则在文件末尾追加读写。

函数返回值:

  • 执行成功时,返回一个指向FILE结构体的指针,指向打开的文件流。如果打开或创建文件成功,该指针非空;
  • 如果打开或创建文件失败,返回值为NULL,表示文件打开失败或者权限不足。

The argument mode points to a string beginning with one of the following sequences (possibly followed by additional characters, as described below):

​ r: Open text file for reading. The stream is positioned at the beginning of the file.

​ r+: Open for reading and writing. The stream is positioned at the beginning of the file.

​ w: Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file.

​ w+: Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.

​ a: Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file.

​ a+: Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.

beginning of the file:文件打开后第一个有效字节的位置;

end of file:文件的最后一个有效字节的下一个位置;

当前位置:读和写都发生在文件的当前位置;

注意事项:

(1)fp = fopen(“tmp”,”readwrite”)语句中的mode变量效果等价于”r”,“r+write”等价于”r+”。

(2)在调用fopen()函数时,需要确保提供合适的文件打开模式,以防止意外地覆盖或清空原有文件内容。

(3)对于fopen()函数返回的文件指针,在进行文件读写操作后,需要调用fclose()函数来关闭文件流,释放相关资源。

文件I/O

文件描述符

通俗点说,文件描述符就是数组下标,一个整型数。

每个文件都有一个唯一标识inode(就像每个人都有个唯一的身份证号),如果使用open()打开一个文件,会获得一个相关联的结构体,这个结构体中包含了操作该文件时所有必要的信息,就像fopen()打开一个文件会返回一个FILE指针,通过这个指针来进行文件的读写,前面获得的结构体也会有个指针,该指针存放在数组中,交给用户的是存放这个指针的数组的下标,即一个整型数。通过这个整型数找到一个指针,通过这个指针找到一个结构体,通过这个结构体可以操作一个文件

这个数组有多大?一个进程最多能打开多少个文件,通过ulimit来查看。

一说到stream,其中三个标准的是stdin,stdout,stderr,一说到fd,其中三个标准的是0,1,2。

文件描述符优先使用当前可用范围内最小的。

如果把下标4的内容复制一份放到下标为6的位置,则下标4和下标6对应的两个指针关联的是同一个结构体,如果close(4),那6那部分还能用吗?如果关联的结构体释放掉了,那下标6的指针就会成为一个野指针。每个结构体当中应该有个计数器,来反映当前结构体被几个指针引用了,如果这个计数器的值不是0,就不free()掉。

open(2)

open()函数用于打开一个已存在的文件或者创建一个新文件,函数原型如下:

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

参数说明:

  • pathname:要打开的文件所在路径的字符串,可以是绝对路径或相对路径。
  • flags:位图,用来指定文件打开的模式和选项,必须包含以下中的一个:O_RDONLY,O_WRONLY,O_RDWR。如果flags中有O_CREAT,用三参形式的open(),没有的话,就用两参形式的open()。
  • mode:创建新文件时,该参数用来指定新文件的权限,它是一个八进制数,通常使用 0644 这样的形式。

返回值:

返回一个整型的文件描述符(File Descriptor),代表打开或创建的文件。

  • 如果打开或创建文件成功,将返回非负整数的文件描述符;
  • 如果出现错误,返回值为-1,并设置全局变量errno来表示具体的错误类型。

注意事项:

  1. 打开文件成功后,务必在不需要使用文件描述符时及时关闭文件,以防止文件描述符泄漏。
  2. 使用open()函数创建文件时,应该在flags中使用O_CREAT选项,并且提供合适的文件权限模式mode,以确保创建的文件具有正确的权限。
  3. 在对文件进行读写操作时,建议进行错误检查和适当的错误处理,以应对可能发生的错误情况。

close(2)

close()函数用于关闭已打开的文件,释放与之关联的系统资源。函数原型如下:

1
2
3
#include <unistd.h>

int close(int fd);

参数说明:

  • fd:表示要关闭的文件描述符。它是open(),socket()等函数返回的一个整型值。

函数返回值:

  • 执行成功时,返回值为0;
  • 如果出现错误,返回值为-1,并设置全局变量errno来表示具体的错误类型。

read(2)

read()函数用于从已打开的文件描述符中读取数据,并将读取的内容存储到指定的缓冲区中。函数原型如下:

1
2
3
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

参数说明:

  • fd:表示要读取的文件描述符(File Descriptor)。
  • buf:是一个指向用于存储读取数据的缓冲区的指针。读取的数据将被存储到这个缓冲区中。
  • count:表示要读取的字节数,即希望从文件中读取的数据量。

函数返回值:

  • read()函数执行成功时,返回实际读取的字节数;(这个值可能小于等于count,因为在某些情况下,可能读取到文件末尾或者发生了错误)
  • 若已到达文件末尾,返回值为0;
  • 若出现错误,返回值为-1,并设置全局变量errno来表示具体的错误类型。

On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.
On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
返回值:读到的字节数;若已到达文件尾端,返回0;若出错,返回-1。

注意事项:

  1. read()函数是一个阻塞调用,如果没有数据可读,程序会阻塞在该函数处等待数据到达或者出现其他特定的事件。
  2. 使用read()函数读取文本文件时,注意文本文件中可能含有换行符\n,所以在打印输出时可能会换行。如果不需要换行,可以在输出中去掉换行符。

write(2)

write()函数用于将数据写入已打开的文件描述符中,将指定的缓冲区数据写入到文件中。函数原型如下:

1
2
3
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

参数说明:

  • fd:表示要写入的文件描述符。
  • buf:是一个指向要写入数据的缓冲区的指针。这个缓冲区中的数据将被写入到文件中。
  • count:表示要写入的字节数,即缓冲区中要写入到文件的数据量。

函数返回值:

  • write()函数执行成功时,返回实际写入的字节数。这个值通常等于count,除非写入遇到错误或遇到了输出限制。
  • 若返回值为-1,表示写入发生错误,并设置全局变量errno来表示具体的错误类型。

On success, the number of bytes written is returned (zero indicates nothing was written). It is not an error if this number is smaller than the number of bytes requested; this may happen for example because the disk device was filled. See also NOTES.
On error, -1 is returned, and errno is set appropriately.
If count is zero and fd refers to a regular file, then write() may return a failure status if one of the errors below is detected. If no errors are detected, or error detection is not performed, 0 will be returned without causing any other effect. If count is zero and fd refers to a file other than a regular file, the results are not specified.
返回值:若成功,返回已写的字节数;若出错,返回-1。

注意事项:

  1. write()函数是一个阻塞调用,如果写入操作被阻塞(例如写入缓冲区已满),程序会等待直到能够继续写入或出现其他特定的事件。
  2. 当写入数据到文件时,注意文件权限和文件打开模式,确保文件能够正确写入。如果文件权限不足或文件未正确打开,写入操作可能会失败。

lseek(2)

lseek()函数用于设置文件的偏移量。程序可以在文件中定位到指定的位置,从而实现对文件的随机访问。函数原型如下:

1
2
3
4
#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

参数说明:

  • fd:表示要进行偏移设置的文件描述符。
  • offset:表示要设置的偏移量。它是一个off_t类型的值,可以是正数、负数或零,用于指定相对于whence参数所指定位置的偏移量。
  • whence:表示偏移量的参考位置,即偏移量的计算方式。它可以取三个值:
    • SEEK_SET:从文件起始位置开始计算偏移量。此时,offset表示从文件起始位置开始的偏移量。
    • SEEK_CUR:从当前文件读写位置开始计算偏移量。此时,offset表示相对于当前位置的偏移量。
    • SEEK_END:从文件末尾开始计算偏移量。此时,offset表示相对于文件末尾的偏移量。

函数返回值:

函数执行成功时,返回设置后的文件偏移量。

  • 若设置成功,返回新的文件偏移量,该值通常等于offset参数;
  • 若出现错误,返回值为-1,并设置全局变量errno来表示具体的错误类型。

The lseek() function allows the file offset to be set beyond the end of the file (but this does not change the size of the file). If data is later written at this point, subsequent reads of the data in the gap (a “hole”) return null bytes (‘\0’) until data is actually written into the gap.

Upon successful completion, lseek() returns the resulting offset location as measured in bytes from the beginning of the file. On error, the value (off_t) -1 is returned and errno is set to indicate the error.
返回值:若成功,返回新的文件偏移量;若出错,返回-1。

注:off_t类型用于指示文件的偏移量,常就是long类型,其默认为一个32位的整数,在gcc编译中会被编译为long int类型,在64位的Linux系统中则会被编译为long long int,这是一个64位的整数,其定义在unistd.h头文件中可以查看。

注意事项:

对于不支持偏移设置的设备或文件(如终端、管道等),lseek()函数可能不起作用,返回值为-1,并设置errno为ESPIPE错误。