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 可以是宏定义,也可以是库函数。
一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。
宏使用多用于内核编程等地方,应用编程建议使用函数。