Linux从0到1——基础IO(上)【文件描述符/重定向/缓冲区】

Linux从0到1——基础IO(上)

  • 1. 预备知识
  • 2. 复习一下常见的C语言文件接口
  • 3. 系统调用接口
    • 3.1 函数传参小技巧——标志位
    • 3.2 使用系统调用接口
      • 3.2.1 open
      • 3.2.2 write
      • 3.2.3 read
  • 4. 文件描述符fd
    • 4.1 fd的本质
    • 4.2 理解struct file结构体
    • 4.3 fd的分配规则
  • 5. 重定向
    • 5.1 引入
    • 5.2 一般的重定向写法——配合函数dup2
    • 5.3 stderr的意义
  • 6. 缓冲区
    • 6.1 预备知识
    • 6.2 看一个样例
    • 6.3 用户缓冲区VS内核缓冲区
    • 6.4 验证缓冲区的存在


1. 预备知识


1. 文件 = 内容 + 属性:

  • 所有对文件的操作都可以分为两种:a. 对内容操作 b. 对属性操作;
  • 内容是数据,属性其实也是数据。存储文件,必须即存储内容又存储数据。这里指的文件默认就是在磁盘中的文件;
  • 进程要访问一个文件时,都是要先把这个文件打开的:
    • 打开前:这个文件就是普通的磁盘文件;
    • 打开后:就是将磁盘文件加载到内存。

2. 一个进程可以打开多个文件吗?多个进程可以打开多个文件吗?

  • 一个进程可以打开多个文件,多个进程可以打开多个文件。所以加载到内存中,被打开的文件可能会存在多个。
  • 既然操作系统在运行时,可能会打开多个文件,那么操作系统一定要对这些文件进行管理——先描述,再组织。
  • 我们大胆猜测一下,一个文件要被打开,一定要先在内核中形成被打开的文件对象(结构体),这些对象又可以通过一定的方式链接起来(链表)。

在这里插入图片描述

3. 文件按照是否被打开,分为:被打开的文件、没有被打开的文件

  • 被打开的文件,存在于内存中;
  • 没有被打开的文件,存在于磁盘中。

4. 本次研究文件操作的本质是:研究进程和被打开文件之间的关系。


2. 复习一下常见的C语言文件接口


1. fopen:

在这里插入图片描述
2. fputs:

在这里插入图片描述

3. 代码实践:

#include<stdio.h>int main()
{// "w": 按照写方式打开,如果文件不存在就创建它,并且每次打开都会清空文件内容// "r": 按照只读的方式打开,文件不存在直接报错// "a": 按照追加方式打开,如果文件不存在就创建它,每次打开不会清空文件内容,会在文件结尾处写入FILE *fp = fopen("log.txt", "w");if (NULL == fp){perror("fopen");return 1;}const char *msg = "hello Linux file\n";fputs(msg, fp);     // 像文件中写入字符串fclose(fp);return 0;
}

3. 系统调用接口

  • 进程打开文件的说法是不准确的,准确的说法应该是,进程通过操作系统打开文件。所以上层的fopenfread等函数在底层一定封装了系统调用接口。

3.1 函数传参小技巧——标志位


1. 先写一段代码:

#include<stdio.h>#define Print1 1         // 0001
#define Print2 (1<<1)   // 0010
#define Print3 (1<<2)   // 0100
#define Print4 (1<<3)   // 1000void Print(int flags)
{if (flags&Print1) printf("hello 1 ");if (flags&Print2) printf("hello 2 ");if (flags&Print3) printf("hello 3 ");if (flags&Print4) printf("hello 4 ");printf("\n");
}int main()
{Print(Print1);Print(Print1|Print2);Print(Print1|Print2|Print3);Print(Print3|Print4);Print(Print4);return 0;
}

2. 编译并运行:

在这里插入图片描述

3. 解释:

  • 对于Print函数来说,它只有一个参数flags,这个参数是标记位;
  • flags一共有32个比特位,这里我们只使用四个比特位,也就是只有四个选项;
  • 定义了四个宏Print*,也是四个选项,他们对应的二进制信息已在代码中写出,通过|的方式,将这些选项组合起来,传给Print函数;
  • Print函数内部,通过if (flags&Print*)的方式,可以判断对应的选项是否传入(对应比特位是否是1),如果传入了,就执行该条if后的代码;
  • 上面说的选项,也叫标志位。flags就是各种标记位的组合。

3.2 使用系统调用接口


3.2.1 open


1. 查看man手册:

在这里插入图片描述

  • flags参数就是各种标志位的组合;
  • pathname就是文件路径;
  • 返回值是int类型的数据,是打开文件的文件描述符fd,关于这个文件描述符,我们后面再详细讲,这里只需要知道,文件描述符的使用方式和C语言接口中的FILE*文件指针一样即可;
  • open失败时,会返回-1,同时错误码被设置;
  • 可以看到open接口有两个,第二个还有一个参数mode,需要我们以8进制形式传入权限。

2. flags对应选项:

在这里插入图片描述

  • O_WRONLY:以只写方式打开;
  • O_CREAT:以只写方式打开时,如果文件不存在,就创建它;
  • O_TRUNC:以只写方式打开时,清空文件内容;
  • O_APPEND:以只写方式打开时,不清空文件,在文件末尾追加内容。

3. 代码实例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{// 打开已经存在的文件,不需要带权限// 打开不存在的文件,需要带权限;如果打开不存在的文件,不带权限,那么这个新文件的权限是乱码// 权限以8进制方案传入int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}// close也是一个系统调用,用于关闭文件,头文件是unistd.h// 参数是fdclose(fd);  return 0;
}
  • 编译并运行:

在这里插入图片描述

  • 问题:为什么我们设置的权限是666,可是创建的log.txt权限却是664?

4. 关于权限:

  • 如果我们打开一个不存在的文件,还不传权限,那么它的文件描述符是乱码。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT);	// 不带权限if (fd < 0){perror("open");return 1;}close(fd);  return 0;
}

在这里插入图片描述

  • 关于3中的问题,答案是有系统默认的权限掩码存在,默认是0002,将对应的权限过滤掉了。我们可以通过umask函数来重新设置权限掩码:

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{umask(0);   // 重新设置权限掩码int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}// close也是一个系统调用,用于关闭文件,头文件是unistd.h// 参数是fdclose(fd);  return 0;
}

在这里插入图片描述

  • 注意:不建议使用上面这种方式重新设置权限掩码,尽量和系统默认权限掩码保持一致。

5. close接口:

  • 关闭哪个文件,就传对应文件的文件描述符即可。

在这里插入图片描述


3.2.2 write


1. 查看man手册:

在这里插入图片描述

2. 代码实例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}const char *msg = "hello file system call\n";// 操作文件write(fd, msg, strlen(msg)); // 要不要传 strlen(msg) + 1,将'\0'也传进去?// 第三个参数不要传 strlen(msg) + 1,'\0'只是C语言层面的概念,不是文件层面的概念// '\0'传进文件,会出现乱码close(fd);  return 0;
}
  • O_WRONLY | O_CREAT的方式打开文件,write默认是覆盖式写入。比如文件中原本有内容aaaa,如果再向文件中写入bb,文件内容就会变为bbaa

3. fopen以w方式打开文件的底层:

int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}close(fd);  return 0;
}

4. fopen以a方式打开文件的底层:

int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}close(fd);  return 0;
}

3.2.3 read


1. 查看man手册:

  • fd:要读取文件的文件描述符;
  • buf:用户自定义的一块空间(缓冲区);
  • count:缓冲区总大小;

在这里插入图片描述

2. 代码实例:

int main()
{int fd = open("log.txt", O_RDONLY);if (fd < 0){perror("open");return 1;}char buffer[1024];read(fd, buffer, 1024);printf("%s\n", buffer);close(fd);return 0;
}

在这里插入图片描述


4. 文件描述符fd


4.1 fd的本质


1. 文件描述符fd的本质,就是数组下标:

在这里插入图片描述

  • 操作系统会为打开的文件创建一个结构体struct file来描述它,然后通过链表的方式将多个打开的文件组织起来;
  • 进程PCB中会有一个struct files_struct *files指针,指向该进程管理打开文件的结构体struct files_struct。其中有一个成员为struct file *fd_array[]指针数组,每一个位置对应一个打开文件的结构体对象struct file
  • 文件描述符fd,其实就是struct file *fd_array[]数组的下标,所以只要拿到对应的文件描述符(数组下标),就可以找到对应的文件;
  • C/C++程序在运行时,会默认打开三个文件,标准输入流stdin,标准输出流stdout,和标准错误流stderr。这三个文件分别对应的硬件设备为,键盘、显示器、显示器。文件描述符数组的0, 1, 2位置,分别对应这几个文件。

2. FILE*到底是什么?

在这里插入图片描述

  • stdinstdoutstderr的都是FILE*类型的指针。

在这里插入图片描述

  • FILE其实就是C语言中提供的一个结构体类型,我们可以大胆猜测一下,FILE的内部必定封装了文件描述符。

关于FILE现在没有办法讲太多。

3. 代码验证:

int main()
{int fda = open("loga.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);printf("stdin->fd: %d\n", stdin->_fileno);	// 这个_fileno成员就是文件描述符printf("stdout->fd: %d\n", stdout->_fileno);printf("stderr->fd:%d \n", stderr->_fileno);printf("fda: %d\n", fda);printf("fdb: %d\n", fdb);printf("fdc: %d\n", fdc);printf("fdd: %d\n", fdd);close(fda);close(fdb);close(fdc);close(fdd);return 0;
}

在这里插入图片描述

  • 可以发现,文件描述符的分配是有一定规律的。

4. 如何理解一切皆文件?

在这里插入图片描述

  • 底层的很多硬件,大多都有两个基本的功能,输入和输出。但是它们的输入和输出方法是不同的;
  • 但是在上层,我们想通过一切皆文件的方式去管理底层不同的硬件,是如何做到的?
  • 比如此时打开一个磁盘文件,OS在上层就会为磁盘文件创建一个struct file对象。里面有两个很重要的内容就是读方法和写方法的指针,指向磁盘文件具体的读写方法;
  • 从此往后,我们再调用磁盘的读写方法时,不用关心底层是如何实现的,直接调用struct file对象中的方法即可,一切皆文件;
  • 这种封装的方式,可以让我们自然联想到C++中的继承和多态。

4.2 理解struct file结构体


在这里插入图片描述

struct file结构体中,必定要存储两个信息:a. 文件的属性 b. 文件的内容。

对文件的操作无非就分为两种,一种是读,一种是写。但是无论读写,都需要先将磁盘中的文件数据加载到文件缓冲区中。

我们在应用层进行的数据读写,本质是将内核缓冲区中的数据进行来回拷贝。


4.3 fd的分配规则


1. 进程默认已经打开了fd为0,1,2的三个文件,我们可以通过0,1,2直接访问:

  • 0,2可以直接使用,从侧面验证了上述结论。
int main()
{char buffer[1024];	// 用户自己定义的缓冲区ssize_t s = read(0, buffer, 1024); // 从键盘读取if (s > 0){write(1, buffer, strlen(buffer));	// 向显示器写入}return 0;
}

2. 文件描述符的分配规则是:从上往下扫描struct file *fd_array[]数组,寻找最小的,没有被使用的位置对应的下标,分配给打开的文件。

  • 关闭0或2,再打开文件,查看分配给新打开文件的fd(先不要关闭1):
int main()
{close(0);//close(2);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}
  • 发现显示器输出结果是: fd: 0 或者 fd: 2
  • 可见,文件描述符的分配规则:在struct file *fd_array[]数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

如果关闭1号文件(显示器),则无法在显示器中看到输出结果。


5. 重定向


5.1 引入


1. 先看代码,观察现象:

int main()
{close(1);	// 先关1int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);printf("stdout->fd: %d\n", stdout->_fileno);// C语言提供的缓冲区问题,先待定!fflush(stdout); // 在close前,刷新缓冲区close(fd);return 0;
}

在这里插入图片描述

  • printf明明是向显示器打印的,怎么打印到了文件log.txt里?
  • 这种现象叫输出重定向>,常见的重定向有:
    • 输出重定向:>
    • 追加重定向: >>
    • 输入重定向:<

2. 解释:

在这里插入图片描述

  • 首先,根据fd的分配规则,关闭1后,再打开新文件log.txt,新文件的fd就是1。文件描述符数组的1号位置,不再指向显示器,而是文件log.txt
  • printf只认文件描述符1,默认向struct file *fd_array[]数组下标为1的位置所指向的文件打印,所以本该打印到显示器上的内容,打印到了文件log.txt中;
  • 一定要在close前刷新缓冲区,因为printf会先将数据放到C语言提供的缓冲区中,刷新缓冲区,才能让缓冲区中的数据换入到文件中;

这里只是粗力度的解释一下为什么要刷新缓冲区,关于缓冲区更多的细节,我们在后面讲解。

3. 输入重定向(一般不这样写):

int main()
{close(0);   int fd = open("log.txt", O_RDONLY); // fd == 0if (fd < 0){perror("open");return 1;}char buffer[1024];	// 用户自己定义的缓冲区,先将数据读到bufferfread(buffer, 1, sizeof(buffer), stdin);    // stdin->fd: 0printf("%s\n", buffer);close(fd);return 0;
}

在这里插入图片描述

4. 追加重定向:

  • 只需要将1中代码中,openO_TRUNC选项改为O_APPEND即可。
  • 不过一般也不这样写。
int main()
{close(1);	// 先关1int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);printf("stdout->fd: %d\n", stdout->_fileno);// C语言提供的缓冲区问题,先待定!fflush(stdout); // 在close前,刷新缓冲区close(fd);return 0;
}

5. 重定向的本质:

  • 上层fd不变,底层fd所指向的内容在改变。

5.2 一般的重定向写法——配合函数dup2


1. dup2函数:

  • oldfd下标指向的内容拷贝给newfd下标指向的内容。

在这里插入图片描述

2. 输出重定向:

int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}dup2(fd, 1);// 如下内容将打印到文件 log.txt 中printf("hello printf\n");fprintf(stdout, "hello fprintf\n");close(fd);return 0;
}

在这里插入图片描述

3. 追加重定向:

int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}dup2(fd, 1);// 如下内容将追加到文件 log.txt 中printf("hello printf\n");fprintf(stdout, "hello fprintf\n");close(fd);return 0;
}

4. 输入重定向:

int main()
{int fd = open("log.txt", O_RDONLY);if (fd < 0){perror("open");return 1;}dup2(fd, 0);char buffer[1024];fread(buffer, 1, 1024, stdin);printf("%s\n", buffer);close(fd);return 0;
}

5. 引用计数f_count:

  • 通过重定向的学习,我们知道了,一个打开的文件,可以被多个struct file*指针指向。如图,log.txt就被两个struct file*指针指向。

在这里插入图片描述

  • 我们在调用close接口关闭一个文件时,如果这个文件还被其他的指针指向,该怎么办,这样不是会互相影响吗?
  • 所以struct file结构体内还设计了一个f_count字段,它是引用计数,记录有多少个指针指向自己。如果f_count不为0,则执行一次closef_count就减一。直到f_count等于0时,才释放log.txt的资源。

5.3 stderr的意义


1. 看代码,观察现象:

int main()
{fprintf(stdout, "hello stdout\n");fprintf(stderr, "hello stderr\n");return 0;
}

在这里插入图片描述

  • 这个问题现在很好解释,因为重定向只是将1位置的指针改成指向log.txt了,但是stderr对应的2位置的指针,还是指向显示器文件,所以第二条fprintf语句还是向显示器打印了。

在这里插入图片描述

  • 想让这两条fprintf语句都往log.txt打印,需要将2位置也重定向了。在命令行中可以直接执行指令./myfile > log.txt 2>&1,其中2>&1表示让1位置的指针覆盖2位置的指针,这样1位置和2位置都指向log.txt了。
  • 所以./myfile > log.txt的完整写法应该是./myfile 1>log.txt

2. stderr的实际运用:

  • 比如在一个日志系统中,我们希望将正常信息和错误信息进行分流,将他们放在不同的文件中。这时候就可以使用输出重定向,将1位置和2位置重定向到不同的文件,将错误信息单独储存起来。

在这里插入图片描述


6. 缓冲区


6.1 预备知识


1. 我们理解的缓冲区:

  • 缓冲区其实就是一部分内存,重要的是搞清楚这一部分内存由谁提供。
    • 用户缓冲区:用户自己提供,用户在程序中自己定义的缓冲区,如char buffer[1024]
    • C语言缓冲区:由C语言提供的,定义在C库中;
    • 内核缓冲区:由操作系统提供的,内核级别的缓冲区。

2. 缓冲区存在的意义:

  • 任何缓冲区存在的目的只有一个,就是提高效率。

在这里插入图片描述

  • 举一个生活中的例子:
    • 假如你住在云南,你要给你远在北京的朋友送一个键盘。在快递还没有出现的时候,你需要先自己坐火车跑到北京,把键盘送到朋友手中,然后自己再跑回来,一来一回花了一个月时间,效率低下。
    • 后来快递出现了,你可以先把键盘给楼下的菜鸟驿站,然后由菜鸟驿站完成送键盘的任务,等键盘到了北京,你的朋友再去自己楼下的菜鸟驿站把键盘拿到手。
    • 整个过程中,键盘从云南到送到北京这个时间成本是不可避免的,这个时间成本由菜鸟驿站承担了。但是你就轻松了很多,在你把键盘送到菜鸟驿站的那一刻,你就可以认为你把键盘送出去了,然后你就可以干自己的事情了。
    • 所以菜鸟驿站的存在,节省了使用者的时间。
    • 菜鸟驿站就像缓冲区,进程先将数据放入缓冲区,然后由缓冲区执行向特定位置传输数据的操作,解放进程。所以,缓冲区的主要作用是——提高使用者的效率

3. 缓冲区的刷新策略:

  • 菜鸟驿站在送快递时,肯定也有自己的配送方式。

    • 比如某一个客户要求紧急配送,驿站就派专机专门送这个快递;
    • 不紧急的快递,派专机送成本太高了,会积累到一定的量后,统一配送。
  • 缓冲区的刷新也有自己的策略:

    • 无缓冲(立即刷新);
    • 行缓冲(行刷新);
    • 全缓冲(缓冲区满了,再刷新)。
  • 上面说的都是缓冲区刷新的一般策略,除此之外还有一些特殊情况:

    • 因为某些场景需要,需要强制刷新缓冲区;
    • 进程退出时,一定要进行缓冲区刷新。

4. 磁盘和显示器的刷新策略:

  • 一般对于显示器文件,会进行行刷新;
  • 对于磁盘文件,采取全缓冲策略。

6.2 看一个样例


1. 观察现象:

int main()
{fprintf(stdout, "C: hello fprintf\n");printf("C: hello printf\n");fputs("C: hello fputs\n", stdout);const char *str = "system call: hello write\n";write(1, str, strlen(str));fork();return 0;
}
  • 向显示器打印:

在这里插入图片描述

  • 重定向,向log.txt文件中打印:

在这里插入图片描述

2. 理解样例:

  • 当我们直接向显示器打印时,显示器的刷新方式是行刷新,并且我们写的所有打印语句后都有\n\n是一种行刷新策略)。在fork函数执行之前,缓冲区中的数据已经全部刷新,缓冲区为空。(包括系统调用接口write,系统内核级别的缓冲区也为空,这个后面说)
  • 重定向到log.txt文件的本质,是向磁盘文件写入,系统对数据的刷新方式就变成了全缓冲。
  • 全缓冲,意味着实际写入的简单数据,不足以把缓冲区写满,无法达到刷新条件。fork函数执行的时候,数据依旧在缓冲区中。
  • 由于write对应的打印内容正常打印了,所以我们可以得出一个结论:我们目前所谈的“缓冲区”和操作系统没有关系,只和C语言本身有关。
  • C/C++提供的缓冲区,里面保存的一定是用户的数据,属于当前进程在运行时自己的数据。当进程将数据交给操作系统后,这个数据就不属于当前进程了,而是属于操作系统。
  • 当进程退出时,一般要强制刷新缓冲区。缓冲区的刷新,本质上也是一种对当前进程一个变量的清空或“写入”操作。
  • fork后,任意一个进程退出的时候,会强制刷新缓冲区(修改变量),此时就会发生写时拷贝,所以我们看到缓冲区中打印的内容多出一份。
  • write是系统调用,没有使用C语言的缓冲区,它会将数据直接写入操作系统。所以这部分数据不属于进程,也就不会发生写时拷贝。

6.3 用户缓冲区VS内核缓冲区


在这里插入图片描述

1. 用户缓冲区:

  • 我们日常接触的最多的是C/C++提供的语言级别的缓冲区;
  • 我们通常说的刷新,指的是将C语言缓冲区中的数据刷新到操作系统中。

2. 内核缓冲区:

  • C语言缓冲区刷新到操作系统中后,实际上是传给了内核缓冲区,内核缓冲区最后还要将数据刷新到磁盘文件上;
  • 不同的操作系统,内核缓冲区的刷新策略不同。

3. 解释printf(“hello printf\n”);这段代码从执行,到在显示器打印的全过程:

  • 首先,hello printf\n这段数据会先通过printf函数写入到C语言缓冲区中,然后printf函数返回,至此它的任务就完成了;
  • 接着,C语言缓冲区中的数据会根据一定的刷新策略,通过write接口,写入操作系统;
  • 写入操作系统,实际上是先写入了stdout对应的内核缓冲区,然后操作系统再根据自己的刷新策略,将内核缓冲区中的数据刷新到磁盘中。

6.4 验证缓冲区的存在


任何情况下,我们调用C语言文件接口的时候,都要有一个FILE*指针。我们都知道FILE结构体中封装了文件描述符fd,其实FILE结构体中也封装了缓冲区。

在在/usr/include/stdio.h中,有:

typedef struct _IO_FILE FILE;

/usr/include/libio.h中,有:

struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封装的文件描述符
#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

验证了缓冲区的存在。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/394901.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BES(恒玄)平台log分析

前言 恒玄软件调试和分析基本是通过日志形式分析的&#xff0c;今天就详细说下日志组成和常用分析方法 1.日志组成解析 bes日志组成一般说由以下组成&#xff1a;tick时钟 模块log打印所在线程编码log内容 [17:31:22.834] 21786/NONE / 2 | CPU USAGE: busy18 light8…

WebStorm格式化JSON,将一行很长的JSON展开

webstorm json格式化插件将一行很长的json展开 在WebStorm中&#xff0c;要展开很长的JSON行&#xff0c;可以使用内置的JSON格式化功能。 打开WebStorm&#xff0c;并打开包含JSON的文件。 选择JSON文件中的任意部分。 按下快捷键 CtrlAltL (Windows/Linux) 或 CmdAltL (Ma…

GPT-4.o mini

https://share.xuzhugpt.cloud/ GPT-4.o mini 目前免费使用 把上面[chatgpt4o-mini-xuzhu]复制到UserToken的文本框中 点击[个人账户] 测试一下哈&#xff0c;看看&#xff1a; GPT-4.o代码有时候还是有严重错误&#xff1a;好奇怎么来的 上面是我写得&#xff0c;下面是GPT写…

01背包问题 c++

题目描述 有一个背包能装的重量maxw(正整数&#xff0c;0≤maxw≤20000)&#xff0c;同时有n件物品(0≤n≤100)(每件物品只有一件&#xff0c;要么拿&#xff0c;要么不拿)&#xff0c;每件物品有一个重量wi(正整数)和一个价值vi(正整数)。要求从这n件物品中任取若干件装入背包…

C++ 简单学习

C简单编译 auto关键字 auto 关键字用于自动类型推导。它允许编译器自动推断变量的类型&#xff0c;使得代码更加简洁和易于编写&#xff0c;尤其是在处理复杂类型或模板编程时。使用 auto 可以避免编写冗长的类型声明&#xff0c;同时减少由于类型不匹配导致的编译错误 auto x…

论文阅读报告: 在时间双向图上查询基于时间的的密集子图 | ICDE 2024

摘要 本文提出了一个新的模型&#xff08;α, β, T&#xff09;-core&#xff0c;用于在时间双向图上寻找凝聚子图。时间双向图中&#xff0c;不同实体之间的关系随着时间的推移而变化。为了提高查询效率&#xff0c;本文提出了顶点分区和时间分区的历史索引&#xff08;VH-I…

Java学习Day24:基础篇14:多线程

1.程序、进程和线程 程序 进程 进程(process)是程序的一次执行过程&#xff0c;或是一个正在执行的程序。是一个动态的过程&#xff1a;有它自身的产 生、存在和消亡的过程。 如&#xff1a; 运行中的QQ运行中的音乐播放器视频播放器等&#xff1b;程序是静态的&#xff0c…

【大模型从入门到精通13】openAI API 构建和评估大型语言模型(LLM)应用1

这里写目录标题 构建和评估大型语言模型&#xff08;LLM&#xff09;应用开发性能评估指标从开发到部署高风险应用LLM应用开发的最佳实践和建议从小处着手快速迭代自动化测试根据应用需求定制评估考虑伦理影响 构建和评估大型语言模型&#xff08;LLM&#xff09;应用 开发和部…

Sqli-labs-master靶场--布尔盲注

目录 1、布尔盲注 2、布尔盲注的流程&#xff08;以靶场less-8为例&#xff09; 2.1输入id尝试是否存在注入点 2.1.1通过以上尝试&#xff0c;联想到可能是布尔盲注 2.2猜测数据库长度 2.3获取数据库名 2.3.1python脚本获取 代码&#xff1a; 获取结果为&#xff1a; …

Hive SQL ——窗口函数源码阅读

前言 使用Starrocks引擎中的窗口函数 row_number() over( )对10亿的数据集进行去重操作&#xff0c;BE内存溢出问题频发&#xff08;忘记当时指定的BE内存上限是多少了.....&#xff09;&#xff0c;此时才意识到&#xff0c;开窗操作&#xff0c;如果使用 不当&#xff0c;反而…

1. js混淆-源码乱码

目录 调试干扰参数逆向 调试干扰 打开开发者工具&#xff0c;首先会进入 setInterval 生成的 debugger 将 uzt.js uyt.js 内容替换 将这两个文件的内容置空&#xff0c;并刷新页面就可以正常调试了 参数逆向 点击翻页&#xff0c;可以发现 https://match.yuanrenxue.cn/api…

Arduino导入实例程序的过程,实例包文件却编译显示缺失文件

参考中实例程序中的readme.txt 导入方式 下面是文档中的使用方式 1.基本信息&#xff1a; 本例程是基于Arduino进行开发的&#xff0c;例程均在E-Paper ESP8266 Driver Board上进行了验证;2.基本使用&#xff1a;方法1&#xff1a;将整个esp8266-waveshare-epd文件夹复制到C…

【Go】通过反射解析对象tag信息,实现简易ORM

反射是运行时&#xff0c;需要在运行时解析类型信息&#xff0c;编译期无法优化这些操作&#xff0c;因此比编译时已知类型信息的直接调用效率要低。 package mainimport ("fmt""reflect""strings" )type Person struct {Name string json:&quo…

PicGo + gitee 免费搭建个人图床

目录 1 图床概念2 使用gitee和PicGo搭建图床流程2.1 下载安装PicGo工具 3 图片上传错误处理3.1 PicGo客户端提示404错误信息图片上传失败3.2 PicGo客户端提示400错误信息图片上传失败 1 图床概念 ​ "图床"是一个网络术语&#xff0c;它指的是一种用于存储和托管图片…

理解张量拼接(torch.cat)

拼接 维度顺序&#xff1a;对于 3D 张量&#xff0c;通常可以理解为 (深度, 行, 列) 或 (批次, 行, 列)。 选择一个dim进行拼接的时候其他两个维度大小要相等 对于三维张量&#xff0c;理解 torch.cat 的 dim 参数确实变得更加抽象&#xff0c;但原理是相同的。让我们通过一…

算法力扣刷题记录 六十九【动态规划基础及509. 斐波那契数】

前言 调整一下做题顺序&#xff0c;多个章节同步进行&#xff0c;穿插练习。可以在各章节的专栏中找同一类。 记录 六十九【动态规划基础】。 一、动态规划理论基础学习 参考学习链接 二、509. 斐波那契数 2.1 题目阅读 斐波那契数 &#xff08;通常用 F(n) 表示&#x…

html+css+js网页设计 中国移动5个页面(带js)

htmlcssjs网页设计 中国移动5个页面&#xff08;带js&#xff09; 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xf…

Cpp中的this指针--复习记录

1.什么是this指针? 每个类都有一个this指针&#xff0c;我们的非静态成员函数可以通过这个this指针来操作对象的成员属性。this指针存储的就是类的实例的地址&#xff0c;this指针时时刻刻指向的都是这个实例对象本身。 由下图可知: 我在主函数中栈上创建了一个类的实例(由操…

【Python-实操】LabelMe to YOLOv8 Converter

LabelMe to YOLOv8 Converter 这是一个 Python 脚本&#xff0c;用于将 LabelMe 标注工具导出的 JSON 文件转换为 YOLOv8 格式的标注文件&#xff0c;并同时在图像上绘制标注的多边形。 功能 读取 LabelMe JSON 文件。解码并显示图像。从 classes.txt 文件加载类别标签。将多…

Java | Leetcode Java题解之第327题区间和的个数

题目&#xff1a; 题解&#xff1a; class Solution {public int countRangeSum(int[] nums, int lower, int upper) {long sum 0;long[] preSum new long[nums.length 1];for (int i 0; i < nums.length; i) {sum nums[i];preSum[i 1] sum;}BalancedTree treap ne…