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, ...);