1、I/O:一切实现的基础(李慧琴课程笔记)

1、I/O:一切实现的基础

1.1、标准I/O类型

Stdio:FILE类型始终贯穿结构体

提问:FILE*类型返回变量是定义在栈区还是堆区还是静态区

首先,肯定是不会放在栈上的,因为比如说你调用fopen函数时,在fopen函数里面肯定会用FILE定义一个变量tmp,当你返回&tmp这个指针时,函数调用完tmp这个变量就消失了,栈上的&tmp这个指针也没了。

其次你如果用static修饰FILE类型变量的话,当这个函数被调用时,这个变量只被定义一次,也就是说这时候你用fopen打开十个文件,前面九个文件的FILE类型变量报废了,直接出错,因此也不可能是静态区。当然,不是所有的函数都这样,最后会介绍区分的小窍门。

最后,答案肯定是堆区了。当是堆区的时候就会定义一个FILE类型的指针,具体如下:

FILE *fopen(const char *path, const char *mode);
{
	FILE *tmp = NULL;
    tmp = malloc(sizeof(FILE));/*在这里就是分配在堆上咯,与其对应的free函数当然就是在fclose函数里面啦,成对调用的*/
	tmp->   = ;/*tmp的初始化*/
	.........
	return tmp;
}

**小窍门:**如果一个函数是成对或者有逆操作,那么基本可以确定函数返回的指针是放在堆上的,如果是没有逆操作的,则有可能是在堆上也有可能是在静态区!

如何查看错误类型:
1.perror(const char * s)------------>打印系统错误信息

void perror(const char *s);
#include<errno.h>
/*以下三个变量是关联过后的私有化后的全局变量,其中就有errno,会关联errno的使用,具体用法就是perror(“fopen()”)*/
const char *sys_errlist[];
int sys_nerr;
int errno;

​ 2.strerror(errno)

#include<string.h>
char *strerror(int errnum);/*因为有一些函数报错是会主动设置errno的,所以一般用法是:以fopen出错为例:fprintf(stderr,"fopen():%s\n",strerror(errno))*/

1.1.1、fgets()及相关函数注意点:

int fgetc(FILE *stream);

char fgets(char *s, int size, FILE *stream);

int getc(FILE *stream);

int getchar(void);

char *gets(char *s);/*能用fgets()函数尽量不要用gets()函数,get()函数比较危险,因为你压根不知道s这个地址空间大小是多少,但fgets就指定了能够接收的大小*/

int ungetc(int c, FILE *stream);

/*fgets擦边球示例O.o*/
#define SIZE 5
 char buf[SIZE];
 fgets(buf,SIZE,stream);/*成功返回指针buff,失败返回NULL*/
 /*属于行缓冲,若读取abcd四个字符,则需要读取两次,因为会有换行符\n*/
 1 -> a b c d '\0'/*第一行*/
 2 -> '\n' '\0'/*第二行*/
 /*如果读取的是abcdef六个字节,则是下面这种情况了,'\0'是行结束符的意思*/
 1 -> a b c d '\0'/*第一行*/
 2 -> e f \n '\0'/*第二行*/
 

1.1.2、 fread(buf,size,nmemb,fp)注意点(fwrite也是如此):

这两个函数主要涉及二进制流的读写,其实也可以用作字节的读写实现

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);/*从stream中读取数据到ptr中去,大小为nmemb个对象乘以size的大小*/

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);/*const表示对原数据只读不写,起到保护作用*/

/*有以下两种情况,尽量使用第一种类型的读写方法,也就是当做fputc和fgetc来使用*/
/*	第一种情况:数据量足够
	第二种情况:数据量只有五个字节
*/
1->fread(buf,1,10,fp);
1.1-> 10 -> 10个字节/*第一种情况:数据量足够*/
1.2-> 5 -> 5个字节/*第二种情况:数据量只有五个字节*/
2->fread(buf,10,1,fp);
2.1-> 1 -> 10个字节/*第一种情况:数据量足够*/
2.2-> 0 -> ???/*第二种情况:数据量只有五个字节,此时就会返回0了,而且也不确定文件里面到底有多少数据,因为这一个对象不够十个字节的体量*/

atoi使字符串转化为整形

1.1.3、snprintf可以防止越界行为、scanf不知道目标位置有多大,可能会出现越界行为。

sprintf(char *str, const char *format, ...);
snprintf(char *str, size_t size, const char *format);

1.1.4、ftell与fseek差别(都是用于控制文件位置指针)

一般不建议使用ftell,因为其只能包含正数部分,例如文件有4G,他只能存2G

/*ftell用于得到文件当前位置*/
long ftell(FILE *stream);
/*用于控制文件位置指针,若offset为-10,whence为SEEK_CUR 则表示文件在当前位置向前偏移十个字节*/
int fseek(FILE *stream, long offset, int whence);
/*rewind用于定位到文件首*/
void rewind(FILE *stream);
/*fseeko和ftello则解决了上面的争议,但使用的时候需要考虑是否有#define _FILE_OFFSET_BITS 64将off_t定义为64个字节*/
off_t ftello(FILE *stream);
int fseeko(FILE *stream, off_t offset, int whence);

1.1.5、fflush

/*当stream传入NULL时,刷新所有输出流*/
int fflush(FILE *stream);

1.1.6、缓冲区的作用(一般写文件默认情况下是全缓冲模式,输出设备默认为行缓冲模式)

缓冲区的作用:大多数情况下是好事,合并系统调用

行缓冲:换行的时候刷新,满了的时候刷新,强制刷新(标准输出是这样的,因为是终端设备)

全缓冲:满了的时候刷新,强制刷新(默认,只要不是终端设备)

无缓冲:如stderr,需要立即输出的内容

/*可以更改缓冲区的模式,其中mode必须为_IONBF、_IOLBF、_IOFBF,一般用不上setvbuf,因为默认为全缓冲,了解就OK*/
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

1.1.7、getline实际上是malloc和realloc的原子操作,是可释放空间的。

例如当开始malloc分配了120个字节,但是不够的话会在realloc120个字节,从而含有240个字节空间。
可以free,属于可控的内存泄露

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char **argv)
{
FILE *fp;
char *linebuf;
size_t linesize;
	if(argc<2)
	{
		fprintf(stderr, "Usage ...\n");
		exit(1);
	}
	fp = fopen(argv[1], "r");
	if (fp = NULL)
	{
		perror("fopen()");
		exit(1);
			
	}
	/*非常必须要初始化这两个变量,不然会报bug*/
	linebuf = NULL;
	linesize = 0;
	while(1)
	{
	
		if(getline(&linebuf, &linesize, fp) < 0)
		{
			break;
			printf("%d\n", strlen(linebuf));
			printf("%d\n", linesize);
		}

	}
	fclose(fp);
//可以加free来释放空间	

	exit(0);
}

1.1.8、临时文件

1 如何不冲突
2 及时销毁
tmpnam比较危险,无法创建一个名字马上open它,可能会被其他进程创建名字并且调用,造成内存泄漏,就是临时文件的冲突。
tmpfile创建一个匿名文件,ls是查询不到的,所有的操作都是围绕FILE指针参数,无需考虑及时销毁的问题(前提是进程不是一直运行下去的),可以调用fclose销毁。

char *tmpnam(char *s);
FILE *tmpfile(void);

1.1.9、补充:fopen()函数

FILE *fopen(const char *path, const char *mode);/*成功返回FILE*指针,失败返回errno,具体的mode参数咱还是看man手册吧,谁记得住啊= =*/

FILE *fdopen(int fd, const char *mode);

FILE *freopen(const char *path, const char *mode, FILE *stream);

1.1.10、补充:fclose()函数

int fclose(FILE *fp)

1.2 系统I/O类型

1.2.1 open函数的实现是用变参实现的,而不是重载实现的

相关知识网站
【基础知识】函数重载_芦苇猫的博客-优快云博客
C语言之变参函数-优快云博客

1.2.2 文件描述符

fopen函数实际上也是调用了一个open函数,其FILE*指针包含了fd这个文件描述符。

一个文件最多可以打开1024个文件描述符,其中有标准输入、输出、错误三个占用三个文件描述符

文件描述符优先使用当前可使用范围内最小的(例如,3,4,5,6,都被使用了,就会接着使用7,如果3被释放了,这时候再去打开一个文件描述符就是3)

1.2.3 open函数

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);/*moede指打开文件的权限,可以和umask配合使用*/

1.2.4 read函数和write函数lseek

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);/*buf没有写的权限*/
off_t lseek(int fd, off_t offset, int whence);

1.2.5 文件IO和标准IO的区别

区别:响应速度;吞吐量。文件IO响应速度更快,标准IO吞吐量更大

面试:如何使一个程序更快?

解答:应该咨询是要响应速度还是吞吐量,一般用户可能吞吐量更大体验更好

提醒:标准IO和文件IO不可以混用!!!

转换:fileno函数可以获得文件描述符

int fileno(FILE *stream);/*获得文件流中的文件描述符*/

fdopen函数可以将文件描述符转化为流

FILE *fdopen(int fd, const char *moce);

1.2.6 文件共享

多个任务同时操作一个文件或者系统完成任务

面试:写一个程序删除文件的第十行

补充函数:truncate/ftruncate

int truncate(const char *path, pff_t length);/*把一个未打开的文件截断到多长*/
int ftruncate(int fd, off_t length);/*把一个已打开的文件截断到多长*/

1.2.7 原子操作

1.2.71 原子:不可分割的最小单位

作用:解决竞争和冲突

1.2.72 dup和dup2

dup2是dup和close的原子操作

在操作文件时尽量还原文件的原始状态,用了malloc就要记得free,重定向完以后就要重定向回去,以免出现错误

int dup(int oldfd);/*将旧的文件描述符复制到一个可用的最小文件描述符上去,如4号文件描述符被复制,此时五号文件描述符已经被使用,那么复制出来的就是六号文件描述符,且指向同一个结构体*/
int dup2(int oldfd, int newfd);/*会将newfd作为oldfd的副本,如果有必要会close掉newfd,然后将oldfd放到newfd的副本上*/

同步:sync、fsync、fdatasync

void sync(void);/*当要结束挂载将正在cache中或者buff中的数据刷新一下*/
int fsync(int fd);/*刷新指定文件描述符的数据*/
int fdatasync(int fd);/*只刷数据,不刷亚数据(例如文件的时间权限等)*/

1.2.73 fcntl管理文件描述符

int fcntl(int fd, int cmd, .../*arg*/)

1.2.74 ioctl()设备相关内容

int ioctl(int d, int request, ...);

1.2.75 /dev/fd/目录: 虚目录,显示的是当前进程下的文件描述符信息

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值