c语言缓冲区的理解

来源:互联网 发布:无锡erp软件 编辑:程序博客网 时间:2024/05/21 05:18

开始深入地了解一下c语言,发现以前对于缓冲区的理解并不清楚,在这里对此作一些深入的了解,后续关于缓冲区的问题也在本篇博客上进行更新。
备注:本篇博客的内容建立在刚阅读的c语言中文网相关内容,网址如下:http://c.biancheng.net/cpp/html/2413.html(需要会员资格查看相关内容)
缓冲区的概念
缓冲区(Buffer),又称为缓存(Cache),就是一块内存区,处于IO设备与CPU之间,用来缓存数据。它使得低速的IO设备和高速的CPU能够协调工作,避免低速的IO设备占用CPU,解放出CPU,使其能够高效率工作。直白地说,在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区。
缓冲区的类型
缓冲区有三种类型:全缓冲、行缓冲和不带缓冲。

1)全缓冲当填满缓冲区后才进行实际IO操作。全缓冲的典型代表是对磁盘文件的读写(读到的文件内容高于缓冲区时,才会真正执行磁盘文件的读写操作)。2)行缓冲遇到换行符\n时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。3)不带缓冲也就是不进行缓冲,一旦有操作,立即进行IO操作标准错误文件stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

缓冲区的刷新(清空)

下列情况会引发缓冲区的刷新:
缓冲区满时;
行缓冲区遇到回车时;
关闭文件;
使用特定函数刷新缓冲区(fflush(stdin))。

缓冲区大小
如果没有设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是512个字节的大小。缓冲区大小由 stdio.h头文件中的宏BUFSIZ 定义。通过如下代码可以查看该值:
printf(“%d”, BUFSIZ);//macOS输出值为1024

对于缓冲区,有一个常用的函数是需要了解的:scanf();

scanf() 是带有缓冲区的。遇到 scanf() 函数,程序会先检查输入缓冲区中是否有数据:
1. 如果没有,等待用户输入。用户从键盘输入的每个字符都会暂时保存到缓冲区,直到按下回车键,输入结束,scanf() 再从缓冲区中读取数据,赋值给变量。
2. 如果有数据,scanf()会直接读取,不会等待用户输入。
3. 当控制字符串不是以%xxx 开头时,回车键就起作用了(不再视为行的结尾符),scanf()会对它进行匹配,只是匹配失败而已。
4. 在以%c的格式输入时,\n不会被视为换行字符,而是会作为1个字符存在缓冲区,被之后的%c格式读入。

上述关于scanf()函数的特性会导致一些看起来很奇怪的问题出现:
如下列代码:

#include <stdio.h>#include <stdlib.h>int main(){    int a=0, b=0;    scanf("a=%d", &a);    scanf("b=%d", &b);    printf("a=%d, b=%d\n", a, b);    return 0;}//输入a=100后换行,终端不等待输入b=..,直接输出a=100, b=0//这里就跟上述的第3条特性相关了,当控制字符串不是以%xxx 开头时,//回车键就不起作用了,scanf()会对它进行匹配,只是匹配失败而已。//当输入a=100换行时,先匹配a=100,之后将a=100从缓冲区删除,然后//匹配\n字符,发现\n并不是不=...的格式,匹配失败,直接运行下一行代码

如果想让上面的代码运行不出现这种逻辑错误,可以将其改为如下形式:

#include <stdio.h>int main(){    int a, b;    scanf("%d", &a);    scanf("%d", &b);    printf("a=%d, b=%d\n", a, b);    return 0;}//输入:100//200//输出:a=100,b=200

上述代码还有一种输入方式,输入100 200 300 400 500 600 700,即不换行,直接用空格,一次性写入缓冲区,scanf()读取数据时,先读取100,赋值给a,将100从缓冲区删除,再读取200,赋值给b,运行下一行代码,在上述程序中,直到代码运行完成,缓冲区仍然还有300 400 500 600 700这些数据,只是没有使用到而已。
至于scanf()不安全的缓冲区问题,与第1、2条缓冲区的特性有关:

c语言中,scanf()中的格式控制符必须与参数类型相同,不然会出现无法理解的错误,如下列代码(至于为什么,现在不去纠结):

#include <stdio.h>int main(){    char a,b;    scanf("%d",&a);    scanf("%d",&b);    printf("%d %d\n", a,b);    return 0;}//输入:12//34//输出:0 34

清空缓存区的方法
scanf() 的缓冲区有时会引发奇怪的问题,多个 scanf() 之间要注意清空缓冲区。清空缓存区存在两种解决方式:

  1. 将缓冲区中的数据丢弃
  2. 将缓冲区中的数据读出来,但不使用。

将缓冲区中的数据丢弃,c语言提供了fflush(stdin)方法,在C语言中,为了便于操作,键盘和显示器也被看作是文件。键盘称为标准输入文件(stdin),显示器称为标准输出文件(stdout)。如下列代码:

#include<stdio.h>int main(){    int a, b;    scanf("%d", &a);    fflush(stdin);    scanf("%d", &b);    printf("a=%d, b=%d\n", a, b);    return 0;}//按照c语言中文网的demo说法,该demo在windows的运行结果应该如下://运行结果://100 200↙//300↙//a=100, b=300//第一个scanf() 读取完成后,将100赋值给变量a,缓冲区中剩下200。然//后调用 fflush() 函数将200从缓冲区中清除。执行到第二个 scanf() //时由于缓冲区中没有数据,所以会等待用户输入。在LinuxGCC下可能无//效,因为C语言标准规定:对于以 stdin 为参数的 fflush() 函数,它//的行为是不确定的,fflush() 操作输入流是对标准C语言的扩充。//上述代码在macbook gcc 运行结果如下://100 200↙//a=100, b=200//也即fflush(stdin)语句无效

将缓冲区中的数据读出来,但不使用
1. 使用下列语句:

//如果没有遇到换行符或者文件结尾符,就继续读出数据,但是不做任何操作,直到遇到换行符或者文件结尾符while((c = getchar()) != '\n' && c != EOF);

2.使用下列语句:

//%*[^\n]:逐个读取缓冲区中的'\n'字符前其它字符,%后面的*表示将读取的这些字符丢弃,遇到'\n'字符时便停止读取。//建议使用该方法scanf("%*[^\n]%*c");

上述两种方式的例子如下:

#include <stdio.h>int main(){    int a, b;    char c;    scanf("%d", &a);    scanf("%*[^\n]%*c");    scanf("%d", &b);    printf("a=%d, b=%d\n", a, b);    while((c=getchar()) != '\n' && c != EOF)        ;    scanf("%d",&a);    while((c=getchar()) != '\n' && c != EOF)        ;    scanf("%d",&b);    printf("a=%d,b=%d\n", a,b);    return 0;}//运行结果://100 200↙//300↙//a=100, b=300//9 99↙//999↙//a=9, b=999

关于缓冲区:还有个缓冲区溢出的问题,暂时不做深究,先列出其定义,维基百科关于缓冲区溢出的定义如下:
缓冲区溢出(buffer overflow),是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。目前OpenBSD、Linux、Windows、Mac OS等操作系统都具有buffer overflow protection(缓存溢出保护/内存位置重新定向)功能[来源请求],在某种程度上可以保护操作系统,但仍还是有办法让溢出的代码到正确的位置上。其是作原理是:内存跟进程在memory中受到保护。内对外的access memory对象位置会被核心(调度器)随机定向,使其无法正确溢出。

原创粉丝点击