APUE01 - I/O(上)

时间:2024-10-18 14:45:04

I/O分为文件I/O与标准I/O。

文件I/O也叫系统调用,标准I/O也叫库函数。

操作系统负责管理和分配所有的计算机资源。为了更好地服务于应用程序,操作系统提供了一组特殊接口——系统调用。

用户程序向操作系统提出请求的接口就是系统调用。所有的操作系统都会提供系统调用接口,只不过不同的操作系统提供的系统调用接口各不相同。

库函数可以说是对系统调用的一种封装,因为系统调用是面对的是操作系统,系统包括Linux、Windows等,如果直接系统调用,会影响程序的移植性,所以这里使用了库函数。

所以,当一个功能可以同时用标准I/O和文件I/O来实现的时候,推荐使用标准I/O。

拿打开文件来说,标准I/O就是 fopen等函数,写出来的程序,在 windows 和 linux 都可以编译运行,但是如果使用 open 的话,在 windows 会出问题。

上面说过,fopen是封装了系统调用,也就是它在 Unix 上是使用 open来实现的,但是在 windows 上是使用 openfile来实现的。

所以,优先使用标准库函数。

fopen

可使用 man 命令来查看详细说明。

fopen 的返回值是一个 FILE 结构体指针,当失败的时候,返回 null,而且会设置 errno。

errno

errno 的一些值定义在 /usr/include/asm-generic/errno-base.h 里面:

#define EPERM   1 /* Operation not permitted */
#define ENOENT   2 /* No such file or directory */
#define ESRCH   3 /* No such process */
#define EINTR   4 /* Interrupted system call */
#define EIO   5 /* I/O error */

看一个例子程序:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("", "r");
    if (fp == NULL)
    {
        fprintf(stderr, "fopen failed! errno = %d\n", errno);
        perror("fopen()");

        int count = 0;
        FILE *fp2 = NULL;

        fp2 = fopen(NULL, "r");
        if (fp2 == NULL)
        {
          // strerror();
            perror("fopen(x.txt)");
            exit(1);
        }
        
    }

    exit(0);
}

运行,输出:

fopen failed! errno = 2
fopen(): No such file or directory
fopen(x.txt): Bad address

与宏定义的说明是对得上的。

从程序里面戳进去,就可以看到 errno 的定义:

# define errno (*__errno_location ())

它实际上是调用了一个函数。

本来想模拟一下 too many open files 的错误,但是没想到 ulimit -a 的设置不生效。惊了!!!

打印错误信息可使用 perror或者 strerror 。

mode

关于打开模式只有一个地方需要注意:

The  mode  string  can also include the letter 'b' either as a last character or as a character between
the characters in any of the two-character strings described above.  This is strictly for compatibility
with ISO C and has no effect; the 'b' is ignored on all  POSIX  conforming  systems,  including  Linux.

因为在 linux 环境下,只有一个流的概念,叫 stream。windows 可能还会分二进制流和文本流。

fgetc 与 getc

C语言中,fgetc 和 getc 都是用于从文件中读取下一个字符的标准库函数。但它们之间的主要区别在于 fgetc 永远是库函数,而 getc 可以是宏定义,也可以是库函数。

一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。

宏使用多用于内核编程等地方,应用编程建议使用函数。