标准 IO
注: 李慧芹老师的视频课程请点这里, 本篇为标准IO一章的笔记, 课上提到过的内容基本都会包含
I/O (Input & Output): 是一切实现的基础
stdio (标准IO)
sysio (系统调用IO / 文件IO)
系统IO是内核接口, 标准IO是C标准库提供的接口, 标准IO内部使用了系统IO
标准IO会合并系统调用, 可移植性好, 因此在两者都可以完成任务的情况下, 优先使用标准IO
stdio 的一系列函数
详细参考man(3);
FILE
类型贯穿始终, FILE类型是一个结构体
fopen()
: 产生FILE
fclose()
fgetc()
fputc()
fgets()
fputs()
fread()
fwrite()
pintf()
一族
scanf()
一族
fseek()
ftell()
rewind()
fflush()
打开操作
// 打开文件操作, 运行成功时, 返回FILE指针, 失败则返回NULL且设置errno
// params:
// @path: 要打开的文件
// @mode: 打开的权限(如: 只读/读写/只写...)
FILE *fopen(const char *path, const char *mode);
const char *
面试题:
char *ptr = "abc"; ptr[0] = 'x'; // 语句2
问: 能否通过语句2得到值为
"xbc"
的字符串?
gcc
编译会报错(修改常量值), 但Turbo C
一类的编译器编译出的程序会运行通过
errno
ubuntu22系统中, 可以执行vim /usr/include/errno.h
来查看相关信息
errno曾经是一个全局变量, 但目前已被私有化, 新建test.c
:
#include <errno.h>
errno;
执行gcc -E test.c
对test.c
进行预处理, 会得到:
// MacOS操作系统上的运行结果:
extern int * __error(void);
(*__error());
// Ubuntu22上的运行结果:
(*__errno_location ());
可以看到, errno已经被转化为宏 (而不是int类型全局变量)
再新建测试程序errno.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
FILE *fp;
fp = fopen();
if (fp == NULL)
{
fprintf(stderr, "fopen() failed! errno = %d\n", errno);
exit(1);
}
puts("OK");
exit(0);
}
编译并运行该程序, 输出结果:
fopen() failed! errno = 2
标准C中定义的errno类型:
类型 | 序号 | 含义 |
---|---|---|
EPERM | 1 | Operation not permitted |
ENOENT | 2 | No such file or directory |
ESRCH | 3 | No such process |
EINTR | 4 | Interrupted system call |
EIO | 5 | I/O error |
ENXIO | 6 | No such device or address |
E2BIG | 7 | Argument list too long |
ENOEXEC | 8 | Exec format error |
EBADF | 9 | Bad file number |
ECHILD | 10 | No child processes |
EAGAIN | 11 | Try again |
ENOMEM | 12 | Out of memory |
EACCES | 13 | Permission denied |
EFAULT | 14 | Bad address |
... | ... | ... |
根据上表中展示的errno类型, 可以得知, 2代表了文件或目录不存在
可以调用perror()
或strerror()
来将errno转化为error message
mode
mode必须以表格中的字符开头
符号 | 模式 |
---|---|
r | 以只读形式打开文件, 打开时定位到文件开始处 |
r+ | 读写形式打开文件, 打开时定位到文件开始处 |
w | 写形式打开文件, 有则清空, 无则创建 |
w+ | 读写形式打开文件, 有则清空, 无则创建 |
a | 追加只写的形式打开文件, 如文件不存在, 则创建文件; 打开时定位到文件末尾处 (文件最后一个有效字节的下一个位置) |
a+ | 追加读写的形式打开文件, 如文件不存在, 则创建文件; 读位置在文件开始处, 而写位置永远在文件末尾处 |
注意:
r
和r+
要求文件必须存在mode可以追加字符
b
, 如rb
/r+b
,b
表示二进制流, 在POSIX环境(包括Linux环境)下,b
可以忽略
面试题:
FILE *fp; fp = fopen("tmp", "r+write"); // 语句2
问: 语句2是否会报错?
并不会, fopen函数只会识别
r+
, 后面的字符会被忽略
FILE *
fopen
返回的FILE结构体指针指向的内存块存在在哪里?
堆上
有逆操作的, 返回指针的函数, 其返回的指针一定指向堆上某一块空间
如无逆操作, 则有可能指向堆, 也有可能指向静态区
关闭操作
由于fopen
返回的指针在堆上, 因此需要有一逆操作释放这一堆上的空间
int fclose(FILE *fp);
小例子
一个进程中, 打开的文件个数的上限?
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main()
{
FILE *fp;
int cnt = 0;
while (1)
{
fp = fopen("tmp", "r");
if (fp == NULL)
{
perror("fopen()");
break;
}
cnt ++;
}
printf("count = %d\n", cnt);
exit(0);
}
运行结果:
fopen(): Too many open files
count = 1021
在不更改当前默认环境的情况下, 进程默认打开三个流: stdin
, stdout
, stderr
ulimit -a
可以查看当前默认环境的资源限制, 其中包括默认最多打开流的个数:
$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7303
max locked memory (kbytes, -l) 251856
max memory size (k