精华内容
下载资源
问答
  • 图详解缓冲区溢出问题

    千次阅读 多人点赞 2020-11-18 23:43:21
    缓冲区溢出一个常见的后果是:黑客利用函数调用过程中程序的返回地址,将存放这块地址的指针精准指向计算机中存放攻击代码的位置,造成程序异常中止。为了防止发生严重的后果,计算机会采用栈随机化,利用金丝雀值...

    蠕虫病毒是一种常见的利用Unix系统中的缺点来进行攻击的病毒。缓冲区溢出一个常见的后果是:黑客利用函数调用过程中程序的返回地址,将存放这块地址的指针精准指向计算机中存放攻击代码的位置,造成程序异常中止。为了防止发生严重的后果,计算机会采用栈随机化,利用金丝雀值检查破坏栈,限制代码可执行区域等方法来尽量避免被攻击。虽然,现代计算机已经可以“智能”查错了,但是我们还是要养成良好的编程习惯,尽量避免写出有漏洞的代码,以节省宝贵的时间!

    1. 蠕虫病毒简介

      蠕虫是一种可以自我复制的代码,并且通过网络传播,通常无需人为干预就能传播。蠕虫病毒入侵并完全控制一台计算机之后,就会把这台机器作为宿主,进而扫描并感染其他计算机。当这些新的被蠕虫入侵的计算机被控制之后,蠕虫会以这些计算机为宿主继续扫描并感染其他计算机,这种行为会一直延续下去。蠕虫使用这种递归的方法进行传播,按照指数增长的规律分布自己,进而及时控制越来越多的计算机。

    2. 缓冲区溢出

      缓冲区溢出是指计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。理想的情况是:程序会检查数据长度,而且并不允许输入超过缓冲区长度的字符。但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区,又被称为“堆栈”,在各个操作进程之间,指令会被临时储存在“堆栈”当中,“堆栈”也会出现缓冲区溢出

    3. 缓冲区溢出举例

    void echo()
    {
      char buf[4];   /*buf故意设置很小*/
      gets(buf);
      puts(buf);
    }
    void call_echo()
    {
      echo();
    }
    

      反汇编如下:

    /*echo*/
    000000000040069c <echo>: 
    40069c:48 83 ec 18         sub $0x18,%rsp  /*0X18 == 24,分配了24字节内存。计算机会多分配一些给缓冲区*/
    4006a0:48 89 e7            mov %rsp,%rdi   
    4006a3:e8 a5 ff ff ff      callq 40064d <gets>
    4006a8::48 89 e7           mov %rsp,%rdi
    4006ab:e8 50  fe ff ff     callq callq 400500 <puts@plt>
    4006b0:48 83 c4 18         add $0x18,%rsp 
    4006b4:c3                  retq 
    
    /*call_echo*/
    4006b5:48 83  ec 08             sub $0x8,%rsp 
    4006b9:b8 00 00 00 00           mov $0x0,%eax
    4006be:e8 d9 ff ff ff           callq 40069c <echo>
    4006c3:48 83 c4 08              add $0x8,%rsp 
    4006c7:c3                       retq
    

      在这个例子中,我们故意把buf设置的很小。运行该程序,我们在命令行中输入012345678901234567890123,程序立马就会报错:Segmentation fault。

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/qq_16933601/article/details/109638145

      要想明白为什么会报错,我们需要通过分析反汇编来了解其在内存是如何分布的。具体如下图所示:

      如下图所示,此时计算机为buf分配了24字节空间,其中20字节还未使用。

    image-20201111215122537

      此时,准备调用echo函数,将其返回地址压栈。

    image-20201111214702010

      当我们输入“0123456789012345678
    9012"时,缓冲区已经溢出,但是并没有破坏程序的运行状态

    image-20201111214811039

      当我们输入:“012345678901234567
    890123"。缓冲区溢出,返回地址被破坏,程序返回 0x0400600。

    image-20201111214914863

      这样程序就跳转到了计算机中其他内存的位置,很大可能这块内存已经被使用。跳转修改了原来的值,所以程序就会中止运行。

      黑客可以利用这个漏洞,将程序精准跳转到其存放木马的位置(如nop sled技术),然后就会执行木马程序,对我们的计算机造成破坏。

    4. 缓冲区溢出的危害

      缓冲区溢出可以执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。第一个缓冲区溢出攻击–Morris蠕虫,发生在二十年前,它曾造成了全世界6000多台网络服务器瘫痪。

      在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出。因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址。带来的危害有两种,一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。

    5. 内存在计算机中的排布方式

      内存在计算机中的排布方式如下,从上到下依次为共享库,栈,堆,数据段,代码段。各个段的作用简介如下:
    image-20201111151446190

      共享库:共享库以.so结尾.(so==share object)在程序的链接时候并不像静态库那样在拷贝使用函数的代码,而只是作些标记。然后在程序开始启动运行的时候,动态地加载所需模块。所以,应用程序在运行的时候仍然需要共享库的支持。共享库链接出来的文件比静态库要小得多。

      :栈又称堆栈,是用户存放程序临时创建的变量,也就是我们函数{}中定义的变量,但不包括static声明的变量,static意味着在数据段中存放变量

      除此之外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中,由于栈的先进后出特点,所以栈特别方便用来保存、恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存,交换临时数据的内存区。在X86-64 Linux系统中,栈的大小一般为8M(用ulitmit - a命令可以查看)。

      :堆是用来存放进程中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态分配到堆上,当利用free等函数释放内存时,被释放的内存从堆中被剔除。

      堆存放new出来的对象,栈里面所有对象都是在堆里面有指向的。假如栈里指向堆的指针被删除,堆里的对象也要释放(C++需要手动释放)。当然现在面向对象程序都有’垃圾回收机制’,会定期的把堆里没用的对象清除出去。

      数据段:数据段通常用来存放程序中已初始化的全局变量和已初始化为非0的静态变量的一块内存区域,属于静态内存分配。直观理解就是C语言程序中的全局变量(注意:全局变量才算是程序的数据,局部变量不算程序的数据,只能算是函数的数据

      代码段:代码段通常用来存放程序执行代码的一块区域。这部分区域的大小在程序运行前就已经确定了,通常这块内存区域属于只读,有些架构也允许可写,在代码段中也有可能包含以下只读的常数变量,例如字符串常量等。

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/qq_16933601/article/details/109638145

      下面举个例子来看下代码中各个部分在计算机中是如何排布的。

    #include <stdio.h>
    #include <stdlib.h>
    
    char big_array[1L<<24];     /*16 MB*/
    char huge_array[1L<<31];    /*2 GB*/
    
    int global = 0;
    
    int useless() {return 0;}
    
    int main()
    {
      void *phuge1,*psmall2,*phuge3,*psmall4;
      int local = 0;
      phuge1 = malloc(1L<<28);    /*256 MB*/
      psmall2 = malloc(1L<<8);    /*256 B*/
      phuge3 = malloc(1L<<32);    /*4 GB*/
      psmall4 = malloc(1L<<8);    /*256 B*/
    }
    
    

      上述代码中,程序中的各个变量在内存的排布方式如下图所示。根据颜色可以一一对应起来。由于了local变量存放在栈区,四个指针变量使用了malloc分配了空间,
    所以存放在堆上,两个数组big_
    array,huge_array存放在数据段,main,useless函数的其他部分存放在代码段中。

    image-20201111153803357

    6. 计算机中越界访问的后果

      下面再看一个例子,看下越界访问内存会有什么结果。

    typedef struct 
    {
      int a[2];
      double d;
    }struct_t;
    
    double fun(int i)
    {
      volatile struct_t s;
      s.d = 3.14;
      s.a[i] = 1073741824;  /*可能越界*/
      return s.d;
    }
    
    int main()
    {
      printf("fun(0):%lf\n",fun(0));
      printf("fun(1):%lf\n",fun(1));
      printf("fun(2):%lf\n",fun(2));
      printf("fun(3):%lf\n",fun(3));
      printf("fun(6):%lf\n",fun(6));
      return 0; 
    }
    

      打印结果如下所示:

    fun(0):3.14
    fun(1):3.14
    fun(2):3.1399998664856
    fun(3):2.00000061035156
    fun(6):Segmentation fault
    

      在上面的程序中,我们定义了一个结构体,其中 a 数组中包含两个整数值,还有 d 一个双精度浮点数。在函数fun中,fun函数根据传入的参数i来初始化a数组。显然,i的值只能为0和1。在fun函数中,同时还设置了d的值为3.14。当我们给fun函数传入0和1时可以打印出正确的结果3.14。但是当我们传入2,3,6时,奇怪的现象发生了。为什么fun(2)和fun(3)的值会接近3.14,而fun(6)会报错呢?

      要搞清楚这个问题,我们要明白结构体在内存中是如何存储的,具体如下图所示。

    结构体在内存中的存储方式

      GCC默认不检查数组越界(除非加编译选项)。这也是C语言的bug之一,越界会修改某些内存的值,得出我们意想不到的结果。即使有些数据相隔万里,也可能受到影响。当一个系统这几天运行正常时,过几天可能就会崩溃。(如果这个系统是运行在我们的心脏起搏器,又或者是航天飞行器上,那么这无疑将会造成巨大的损失!)

      如上图所示,对于最下面的两个元素,每个块代表 4 字节。a数组占用8个字节,d变量占用8字节,d排布在a数组的上方。所以我们会看到,如果我引用 a[0] 或者 a[1],会按照正常修改该数组的值。但是当我调用 fun(2) 或者 fun(3)时,实际上修改的是这个浮点数 d 所对应的内存位置。这就是为什么我们打印出来的fun(2)和fun(3)的值如此接近3.14。

      当输入 6 时,就修改了对应的这块内存的值。原来这块内存可能存储了其他用于维持程序运行的内容,而且是已经分配的内存。所以,我们程序就会报出Segmentation fault的错误。

    7. 避免缓冲区溢出的三种方法

      为了在系统中插入攻击代码,攻击者既要插入代码,也要插入指向这段代码的指针。这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。在过去,程序的栈地址非常容易预测。对于所有运行同样程序和操作系统版本的系统来说,在不同的机器之间,栈的位置是相当固定的。因此,如果攻击者可以确定一个常见的Web服务器所使用的栈空间,就可以设计一个在许多机器上都能实施的攻击。

    7.1 栈随机化

      栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多机器都运行同样的代码,它们的栈地址都是不同的。实现的方式是:程序开始时,在栈上分配一段0 ~ n字节之间的随机大小的空间,例如,使用分配函数alloca在栈上分配指定字节数量的空间。程序不使用这段空间,但是它会导致程序每次执行时后续的栈位置发生了变化。分配的范围n必须足够大,才能获得足够多的栈地址变化,但是又要足够小,不至于浪费程序太多的空间。

    int main()
    {
    	long local;
    	printf("local at %p\n",&local);
    	return 0;
    }
    

      这段代码只是简单地打印出main函数中局部变量的地址。在32位 Linux上运行这段代码10000次,这个地址的变化范围为0xff7fc59c到0xffffd09c,范围大小大约是223{2^{23}}。在64位 Linux机器上运行,这个地址的变化范围为0x7fff0001b698到0x7ffffffaa4a8,范围大小大约是 232{2^{32}}

      其实,一个好的黑客专家,可以使用暴力破坏栈的随机化。对于32位的机器,我们枚举215=32768{2^{15}} = 32768个地址就能猜出来栈的地址。对于64位的机器,我们需要枚举224=16777216{2^{24}} = 16777216次。如此看来,栈的随机化降低了病毒或者蠕虫的传播速度,但是也不能提供完全的安全保障。

    7.2 检测栈是否被破坏

      计算机的第二道防线是能够检测到何时栈已经被破坏。我们在echo函数示例中看到,当访问缓冲区越界时,会破坏程序的运行状态。在C语言中,没有可靠的方法来防止对数组的越界写。但是,我们能够在发生了越界写的时候,在造成任何有害结果之前,尝试检测到它。

      GCC在产生的代码中加人了一种栈保护者机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀( canary)值,如下图所示:

    image-20201112085448688

      这个金丝雀值,也称为哨兵值,是在程序每次运行时随机产生的,因此,攻击者很难猜出这个哨兵值。在恢复寄存器状态和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者该函数调用的某个函数的某个操作改变了。如果是的,那么程序异常中止。

    image-20201112085829359

    英国矿井饲养金丝雀的历史大约起始1911年。当时,矿井工作条件差,矿工在下井时时常冒着中毒的生命危险。后来,约翰·斯科特·霍尔丹(John Scott Haldane)在经过对一氧化碳一番研究之后,开始推荐在煤矿中使用金丝雀检测一氧化碳和其他有毒气体。金丝雀的特点是极易受有毒气体的侵害,因为它们平常飞行高度很高,需要吸入大量空气吸入足够氧气。因此,相比于老鼠或其他容易携带的动物,金丝雀会吸入更多的空气以及空气中可能含有的有毒物质。这样,一旦金丝雀出了事,矿工就会迅速意识到矿井中的有毒气体浓度过高,他们已经陷入危险之中,从而及时撤离。

      GCC会试着确定一个函数是否容易遭受栈溢出攻击,并且自动插入这种溢出检测。实际上,对于前面的栈溢出展示,我们可以使用命令行选项“-fno- stack- protector”来阻止GCC产生这种代码。当用这个选项来编译echo函数时(允许使用栈保护),得到下面的汇编代码

    /*void echo */
    subq $24,%rsp Allocate 24 bytes on stack
    movq  %fs:40,%rax  Retrieve canary 
    movq %rax,8(%rsp) Store on stack
    xorl %eax, %eax Zero out register    //从内存中读出一个值
    movq %rsp, %rdi  Compute buf as %rsp 
    call gets Call gets 
    movq ‰rsp,%rdi Compute buf as %rsp
    call puts Call puts 
    movq 8(%rsp),%rax Retrieve canary 
    xorq %fs:40,%rax Compare to stored value   函数将存储在栈位置处的值与金丝雀值做比较
    je .L9  If =, goto ok 
    call __stack_chk_fail Stack corrupted  
    .L9
    addq $24,%rsp Deallocate stack space 
    ret
    

      这个版本的函数从内存中读出一个值(第4行),再把它存放在栈中相对于%rsp偏移量为8的地方。指令参数各fs:40指明金丝雀值是用段寻址从内存中读入的。段寻址机制可以追溯到80286的寻址,而在现代系统上运行的程序中已经很少见到了。将金丝雀值存放在一个特殊的段中,标记为只读这样攻击者就不能覆盖存储金丝雀值。在恢复寄存器状态和返回前,函数将存储在栈位置处的值与金丝雀值做比较(通过第10行的xorq指令)。如果两个数相同,xorq指令就会得到0,函数会按照正常的方式完成。非零的值表明栈上的金丝雀值被修改过,那么代码就会调用一个错误处理例程。

      栈保护很好地防止了缓冲区溢出攻击破坏存储在程序栈上的状态。一般只会带来很小的性能损失。

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/qq_16933601/article/details/109638145

    7.3 限制可执行代码区域

      最后一招是消除攻击者向系统中插入可执行代码的能力。一种方法是限制哪些内存区域能够存放可执行代码。在典型的程序中,只有保存编译器产生的代码的那部分内存才需要是可执行的。其他部分可以被限制为只允许读和写。

      许多系统都有三种访问形式:读(从内存读数据)、写(存储数据到内存)和执行(将内存的内容看作机器级代码)。以前,x86体系结构将读和执行访问控制合并成一个1位的标志,这样任何被标记为可读的页也都是可执行的。栈必须是既可读又可写的,因而栈上的字节也都是可执行的。已经实现的很多机制,能够限制一些页是可读但是不可执行的,然而这些机制通常会带来严重的性能损失。

    8. 总结

      计算机提供了多种方式来弥补我们犯错可能产生的严重后果,但是最关键的还是我们尽量减少犯错。

      例如,对于gets,strcpy等函数我们应替换为 fgets,strncpy等。在数组中,我们可以将数组的索引声明为size_t类型,从根本上防止它传递负数。此外,还可以在访问数组前来加上num小于ARRAY_MAX 语句来检查数组的上界。总之,要养成良好的编程习惯,这样可以节省很多宝贵的时间。同时最后也推荐两本相关书籍如下所示。

    代码大全(第二版)
    高质量程序设计指南

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/qq_16933601/article/details/109638145

    展开全文
  • 按照官方教程一步一步做,无法显示缓冲区,也无法生成图层,请各位高手指教,谢谢。
  • ArcGIS缓冲区分析停止工作

    千次阅读 2019-04-27 11:22:59
    ArcGIS缓冲区分析时停止工作,知道哪里的问题呀

    ArcGIS缓冲区分析时停止工作,不知道哪里的问题呀
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 清除缓冲区

    千次阅读 2010-08-01 21:38:00
    输入输出缓冲区的概念(C++用的一些)  我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对...
    清空缓冲区的方法
    清空缓冲区的方法 1.输入输出缓冲区的概念(C++用的多一些)
        我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标发送数据的话。数据目标获得第一个字符,便将它显示。然后从端口读取下一个字符,可是这时就不能保证数据源向端口发送的恰好是第二个字符(也许是第三个,而第二个已经在数据目标显示时发送过了)。这样的话就不能保证输出的数据能完整的被数据目标所接受并处理。
          为了解决这个问题,我们需要在数据源与数据目标中间放置一个保存完整数据内容的区域,那就是 “缓冲区”。这样的话,数据源可以不考虑数据目标正在处理哪部分数据,只要把数据输出到缓冲区就可以了,数据目标也可以不考虑数据源的发送频率,只是从缓冲区中依次取出下一个数据。从而保证了数据发送的完整性,同时也提高了程序的效率。
         当然getch(),getche()没有用到缓冲区。
    2.几个函数的区别
        首先不要忘了,要用getch()必须引入头文件conio.h,以前学C语言的时候,我们总喜欢用在程序的末尾加上它,利用它来实现程序运行完了暂停不退出的效果。如果不加这句话,在TC2.0的环境中我们用Ctrl+F9编译并运行后,程序一运行完了就退回到TC环境中,我们根本来不及看到结果,这时要看结果,我们就要按Alt+F5回到DOS环境中去看结果,这很麻烦。而如果在程序的结尾加上一行getch();语句,我们就可以省掉会DOS看结果这个步骤,因为程序运行完了并不退出,而是在程序最后把屏幕停住了,按任意键才退回到TC环境中去。
        那我们来看看getch()到底起的什么作用,getch()实际是一个输入命令,作用是从键盘接收一个字符,而且并不把这个字符显示出来,就是说,你按了一个键后它并不在屏幕上显示你按的什么,而继续运行后面的代码,所以我们在C++中可以用它来实现“按任意键继续”的效果,即程序中遇到getch ();这行语句,它就会把程序暂停下来,等你按任意键,它接收了这个字符键后再继续执行后面的代码。
        你也许会问,为什么我们在C++中就没有在程序的末尾加上getch(),解释是,软件总是不断更新的,不好的地方当然要进行改正,getch()加在程序末尾,它又不赋值给任何变量,所以它在这个地方完全是垃圾代码,与程序无关。C++中考虑到这一点,于是在每次程序运行完了并不退出,而是自动把屏幕停下来,并显示“press any key...”叫你按任意键退出,这就好比C++在它的环境中运行程序,在程序的末尾自动加上了一行getch();语句,并且在这行语句前还添加了一行输出语句cout<<"press any key...";来提示你程序结束了,按任意键继续。
        实际上我们编译好的程序在程序结束了本身是不会停下来的,我们可以在编译产生的Debug目录中找到这个编译好的应用程序(扩展名exe),在文件夹中双击运行它,你会发现屏幕闪了一下MS-DOS窗口就关闭了,因为程序运行完就自动退出了,回到了windows环境,当然,如果我们在DOS环境中运行这个程序,我们就可以直接在看到DOS屏幕上看到程序运行结果,因为程序运行完后并不清屏。但是,visual stdio.net2003有返回到了tc那样的情况,你必需要有个getch()才行。
        getche()和getch()很相似,它也需要引入头文件conio.h,那它们之间的区别又在哪里呢?不同之处就在于getch()无返回显示,getche()有返回显示。就这么一点看看下面的例子:

    #include<stdio.h>
    #include<conio.h>
    void main()
    {
        char ch;
        for(int i=0;i<5;i++)
        {
            ch=getch();
            printf("%c",ch);
        }
    }

      首先这是个连续5次的循环来实现5次停顿,等待我们输入,我们编译并运行这个程序,假设我们分别输入abcde,屏幕上显示的结果是abcde,这个 abcde并不是在ch=getch();中输出的,我们把printf("%c",ch);这行语句去掉,就会发现我们按5次任意键程序就结束了,但屏幕上什么都没有显示。
        然后我们在把代码中的getch()换成getche()看看有什么不同,我们还是分别输入abcde,这时屏幕上显示的结果是aabbccddee,我们把printf("%c",ch);这行语句再去掉看看,显示的结果就是abcde了,说明程序在执行ch=getche();这条语句的时候就把我们输入的键返回显示在屏幕上,有无回显就是它们的唯一区别。
        有人会说,既然是C的函数库中的,那么就应该淘汰了,我们还研究它,还用它干嘛?但是我发现还是有用着它的地方,否则我也不会在这里说这么多来耽误大家的时间。我就举个例子吧,程序如下:

    #include<stdio.h>
    #include<conio.h>
    void main()
    {
        char ch='*';
        while(ch=='*')
        {
            printf("/n按 * 继续循环,按其他键退出!");
            ch=getch();
        }
        printf("/n退出程序!");
    }

      我们可以在这个循环体中添加我们想要的功能,程序中按*继续循环,其他任意键退出,而且利用getch()无回显的特性,我们不管按什么,都不会在屏幕上留下痕迹,使我们的界面达到美观效果,如果还有更好的办法实现这个功能。例子:

    void main()
    {
        char c, ch;
        c=getch(); /*从键盘上读入一个字符不回显送给字符变量c*/
        putchar(c); /*输出该字符*/
        ch=getche(); /*从键盘上带回显的读入一个字符送给字符变量ch*/
        putchar(ch);
        printf("/n/n");
    }

    值得注意的是前面两个函数都是从键盘读入数据!
    还有getchar是很值得研究的:getchar()是stdio.h中的库函数,它的作用是从stdin流中读入一个字符,也就是说,如果stdin有数据的话不用输入它就可以直接读取了。而getch()和getche()是conio.h中的库函数,它的作用是从键盘接收字符。getchar带有显示。
    与前面两个函数的区别在于: getchar()函数等待输入直到按回车才结束(前提是缓冲区没有数据),回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。

    #include<stdio.h>
    #include<conio.h>
    void main()
    {
        char c;
        c=getchar(); /*从键盘读入字符直到回车结束*/
               //getchar()在这里它只返回你输入字符串的第一个字符,并把返回值赋值给c

        putchar(c); /*显示输入的第一个字符*/
        printf("/n/n");
    }

        例四:呵呵,这个程序你运行一下,相信你又会有疑问了。这个就是从缓冲区中读取了例子。第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。

    #include<stdio.h>
    #include<conio.h>
    void main()
    {
        char c;
        while ((c=getchar())!='/n') /*每个getchar()依次读入一个字符*/
            printf("%c",c); /*按照原样输出*/
        printf("/n/n");
    }

      程序运行时,首先停下来,等你输入一串字符串,输入完毕后,它把你输入的整个字符串都输出来了,咦,你不是说getchar()只返回第一个字符么,这里怎么?
    因为我们输入的字符串并不是取了第一个字符就把剩下的字符串丢掉了,它还在我们的内存中,就好比,开闸放水,我们把水放到闸里去以后,开一次闸就放掉一点,开一次就放掉一点,直到放光了为止,这里开闸动作就相当于调用一次getchar()。我们输入的字符串也是这么一回事,首先我们输入的字符串是放在内存的缓冲区中的,我们调用一次getchar()就把缓冲区中里出口最近的一个字符输出,也就是最前面的一个字符输出,输出后,就把它释放掉了,但后面还有字符串,所以我们就用循环把最前面的一个字符一个个的在内存中释放掉,直到不满足循环条件退出为止。
    例子中循环条件里的'/n'实际上就是你输入字符串后的回车符,所以意思就是说,直到遇到回车符才结束循环,而getchar()函数就是等待输入(或缓冲区中的数据)直到按回车才结束,所以实现了整个字符串的输出。当然,我们也可以把循环条件改一下,比如while ((c=getchar())!='a'),什么意思呢,意思就是遇到字符'a'就停止循环,当然意思是如果你输入“12345a213123/n”那么只会输出到a,结果是12345a。
    再次注意:用getchar()它是从“流”中间去读取,所以第一个getchar()接受的是刚刚中断的流队列中即将出列的第一个字符(不限于回车符,上面举过例子了),如果流队列不为空,执行getchar()就继续放水,直到把回车符也放空为止,空了之后再在执行getchar()就停下等待你的输入了;我们用getch()为什么每次都是等待用户的输入呢?因为getch()是从键盘接收,即时的接收,并不是从stdin流中去读取数据。
    补充:按键盘上的回车产生了2个字符:回车符('/r')和换行符('/n')。回车符'/r'(CR:carriage return:倒车)使光标回到这行的首部,换行符('/n')(new line)然后再换行。
    所以当输入字符'w',并按下回车键以后。首先得到回车符。那个getchar函数结束了。但是还存在一个换行符。所以如果用getchar()来做判断的时候。最好再写一次getchar()清除缓冲区的'/n'.
    3.如何清空输入缓冲区的内容?
    如果我想让getchar()每次都能够等待用户输入的话就要清空缓冲区,下面就介绍方法(不同平台)
    C 标准规定 fflush()函数是用来刷新输出(stdout)缓存的。对于输入(stdin),它是没有定义的。但是有些编译器也定义了 fflush( stdin )的实现,比如微软的VC。其它编译器是否也定义了 fflush( stdin )的实现应当查找它的手册。GCC编译器没有定义它的实现,所以不能使用 fflush( stdin )来刷新输入缓存。
    对于没有定义 fflush( stdin )的编译器,可以使用 fgets()函数来代替它(比用 getchar()、scanf()等函数通用性好)。可以这样忽略输入流中留下的回车等其它输入,从而使下一次的输入总保持一个“干净”的状态。(这个是任何平台下都可以的)
    // ...
    char sbuf[1024];
    // ...
    fgets( sbuf, 1024, stdin );
    // ...
    在windows 的vc下面就可以这样了:
    for(int i=0;i<10;++i)
    {
        char ch=getchar();
        fflush(stdin); //每次都会有等待状态了
    }
    4.总结主要看getch(),getche()的是否显示,getchar()是读取流,而且和前面两个函数不是一个库。掌握清空缓冲区的方法。
    二。fflush(   FILE   *   pStream   )     清空一个流 
      pStream可以是stdin, stdout, stderr, stdprn, stdaux
      flushall()     清空所有流  
      需要包含   stdio.h

    三。

    int n;
    int ret;
    do {
        printf( "Input an integer: " );
        ret = scanf( "%d", &n );
        while ( getchar() != '/n' ); /* Clear the input buffer */
    } while ( ret != 1 );
    /* 执行这一段函数 */

    当用户如果输入一个数字的时候,那么这个时候n定义的是一个整型就将这个整型接收
    ret = scanf( "%d", &n );的意思不等于ret=n;
    而是当n接收到一个整型值时候ret=1;
    while ( ret != 1 );跳出循环
    如果当用户输入一个字符类型的数据,那么这个时候 n已定义为一个整型就无法接收了
    所以n没有接收到值此时候ret=0;
    而getchar正是来接受字符的,当用户输入了回车('/n')后接收完毕
    跳出while ( getchar() != '/n' ); 注意这里的while 顺环体为空语句

    这个时候while ( ret != 1 );(因为ret=0,条件为真继续执行该循环)
    所以说如果你输入了一个非int类型
    那么接下来的又会
    printf( "Input an integer: " );
    ret = scanf( "%d", &n );
    (直到输入为int类型止)

     
    四。
    虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。

    /* C 版本 */
    #include <stdio.h>

    int main( void )
    {
        int i, c;
        for (;;) {
            fputs("Please input an integer: ", stdout);
            if ( scanf("%d", &i) != EOF ) { /* 如果用户输入的不是 EOF */
                /* while循环会把输入缓冲中的残留字符清空 */
                /* 可以根据需要把它改成宏或者内联函数 */
                /* 注:C99中也定义了内联函数,gcc3.2支持 */
                while ( (c=getchar()) != '/n' && c != EOF ) {
                      ;
                } /* end of while */
            }
            printf("%d/n", i);
        }
        return 0;
    }

    /* C++ 版本 */
    #include <iostream>
    #include <limits> // 为了使用numeric_limits

    using std::cout;
    using std::endl;
    using std::cin;
    int main( )
    {
        int value;
        for (;;) {
        cout << "Enter an integer: ";
        cin >> value;
        /* 读到非法字符后,输入流将处于出错状态,
         * 为了继续获取输入,首先要调用clear函数
         * 来清除输入流的错误标记,然后才能调用
         * ignore函数来清除输入缓冲区中的数据。 */

        cin.clear( );
        /* numeric_limits<streamsize>::max( ) 返回缓冲区的大小。
         * ignore 函数在此将把输入缓冲区中的数据清空。
         * 这两个函数的具体用法请自行查询。 */

        cin.ignore( std::numeric_limits<std::streamsize>::max( ), '/n' );
        cout << value << '/n';
        }
        return 0;
    }

    展开全文
  • java生成线缓冲区的代码

    千次阅读 2018-11-15 14:24:53
    因为项目需要要写一个电子围栏,分析之后其实就是Gis空间分析的线性缓冲区,线缓冲区生成的坐标我放在android手机地图显示,可以直观的看到这个算法的结果是否正确,下面是效果图,有需要的可以看看。 下面是核心...

    因为项目需要要写一个电子围栏,分析之后其实就是Gis空间分析的线性缓冲区,线缓冲区生成的坐标我放在android手机地图显示,可以直观的看到这个算法的结果是否正确,下面是效果图,有需要的可以看看。

    下面是核心代码

    public static String getElectricFenceEdgeCoords(List<RoutePoint> points, double radius) {
            // 参数处理
            // 分别生成左侧和右侧的缓冲区边界点坐标串
            List<LatLng> coords = new ArrayList<>();
            for (int i = 0; i < points.size(); i++) {
                coords.add(points.get(i).point);
            }
            String leftBufferCoords = getLeftBufferEdgeCoords(coords, radius);
            // 反序
            Collections.reverse(coords);
            String rightBufferCoords = getLeftBufferEdgeCoords(coords, radius);
            return leftBufferCoords + "," + rightBufferCoords;
        }
    
        /**
         * 根据给定的一系列有顺序的坐标,逆时针生成轴线左侧的缓冲区边界点
         *
         * @param coords 一系列有顺序的坐标
         * @param radius 缓冲区半径
         * @return 缓冲区的边界坐标
         */
        private static String getLeftBufferEdgeCoords(List<LatLng> coords, double radius) {
            // 参数处理
            if (coords.size() < 1)
                return "";
    
            // 计算时所需变量
            double alpha = 0.0;// 向量绕起始点沿顺时针方向旋转到X轴正半轴所扫过的角度
            double delta = 0.0;// 前后线段所形成的向量之间的夹角
            double l = 0.0;// 前后线段所形成的向量的叉积
    
            // 辅助变量
            StringBuilder strCoords = new StringBuilder();
            double startRadian = 0.0;
            double endRadian = 0.0;
            double beta = 0.0;
            double x = 0.0, y = 0.0;
    
            // 第一节点的缓冲区,在此程序中只生成2点
            {
                alpha = Topology.getQuadrantAngle(coords.get(0), coords.get(1));
                startRadian = alpha + Math.PI;
                endRadian = alpha + (3 * Math.PI) / 2;
                strCoords.append(getBufferCoordsByRadian(coords.get(0), startRadian, endRadian, radius));
            }
    
            // 中间节点
            for (int i = 1; i < coords.size() - 1; i++) {
                alpha = Topology.getQuadrantAngle(coords.get(i), coords.get(i + 1));
                delta = Topology.getIncludedAngel(coords.get(i - 1), coords.get(i), coords.get(i + 1));
                l = getVectorProduct(coords.get(i - 1), coords.get(i), coords.get(i + 1));
                if (l > 0) {
                    startRadian = alpha + (3 * Math.PI) / 2 - delta;
                    endRadian = alpha + (3 * Math.PI) / 2;
                    if (strCoords.length() > 0)
                        strCoords.append(",");
                    strCoords.append(getBufferCoordsByRadian(coords.get(i), startRadian, endRadian, radius));
                } else if (l < 0) {
                    beta = alpha - (Math.PI - delta) / 2;
                    x = coords.get(i).getLatitude() + radius * Math.cos(beta);
                    y = coords.get(i).getLongitude() + radius * Math.sin(beta);
                    if (strCoords.length() > 0)
                        strCoords.append(",");
                    strCoords.append(x + "," + y);
                }
            }
    
            // 最后一个点,在此程序中只生成2点
    
            {
                alpha = Topology.getQuadrantAngle(coords.get(coords.size() - 2), coords.get(coords.size() - 1));
                startRadian = alpha + (3 * Math.PI) / 2;
                endRadian = alpha + 2 * Math.PI;
                if (strCoords.length() > 0)
                    strCoords.append(",");
                strCoords.append(getBufferCoordsByRadian(coords.get(coords.size() - 1), startRadian, endRadian, radius));
            }
            return strCoords.toString();
        }
    
        /**
         * 获取指定弧度范围之间的缓冲区圆弧拟合边界点
         *
         * @param center      指定拟合圆弧的原点
         * @param startRadian 开始弧度
         * @param endRadian   结束弧度
         * @param radius      缓冲区半径
         * @return 缓冲区的边界坐标
         */
        private static String getBufferCoordsByRadian(LatLng center, double startRadian, double endRadian, double radius) {
            double gamma = Math.PI / 9;
            /// 6
            StringBuilder strCoords = new StringBuilder();
            double x = 0.0, y = 0.0;
            for (double phi = startRadian; phi <= endRadian + 0.000000000000001; phi += gamma) {
                x = center.getLatitude() + radius * Math.cos(phi);
                y = center.getLongitude() + radius * Math.sin(phi);
                if (strCoords.length() > 0)
                    strCoords.append(",");
                strCoords.append(x + "," + y);
            }
            return strCoords.toString();
        }
    
        /**
         * 获取相邻三个点所形
         *
         * @param preCoord  第一个节点坐标成的两个向量的交叉乘积
         * @param midCoord  第二个节点坐标
         * @param nextCoord 第三个节点坐标
         * @return 相邻三个点所形成的两个向量的交叉乘积
         */
        private static double getVectorProduct(LatLng preCoord, LatLng midCoord, LatLng nextCoord) {
            return (midCoord.getLatitude() - preCoord.getLatitude()) * (nextCoord.getLongitude() - midCoord.getLongitude())
                    - (nextCoord.getLatitude() - midCoord.getLatitude()) * (midCoord.getLongitude() - preCoord.getLongitude());
        }

    最后附上完整代码下载的地址,用eclipse可以打开,下载的代码没有可视化,只返回了坐标数据。

    下载地址https://download.csdn.net/download/ymszzu/10787035

    展开全文
  • 1. 距离制图-创建缓冲区 1.1 点要素图层的缓冲区分析 在 ArcMap 中新建地图文档,加载图层:StudyArea ,point 打开 Arctoolbox,执行命令<Spatial Analyst Tools>-<Distance>-<欧几里德距离>...
  • ArcGIS缓冲区分析出现问题

    万次阅读 2018-04-20 16:11:46
    求问大神们,ArcGIS在做缓冲区分析时,出现如下问题怎么解?就是在选择融合类型为ALL后就成这样了,显示正常的分析结果,是什么原因哇啊啊啊,怎么解决呢?希望有大神指点 谢谢谢谢谢谢啦...
  • LiveRTMP 之RTMP直播高效推送缓冲区

    千次阅读 2016-10-31 20:52:00
    EasyRTMP的推送缓冲区设计EasyRTMP内部也同样采用的环形缓冲的设计方法,将音视频数据都同时存入缓冲区,再由发送者从缓冲区中获取数据进行发送,这样就形成了一个异步、生产者、消费者的过程,上层调用者只需要将...
  • 区分缓冲区是否为满的策略 总是保持一个存储单元为空 使用数据计数 镜像指示位 java实现 为什么要有环形缓冲器 当有大量数据的时候,我们能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,...
  • 环形缓冲区的设计与实现

    万次阅读 2010-04-23 13:17:00
    环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在一个视频处理的机制中,环形缓冲区就可以理解为数据码流的通道,每一个通道都对应着一个环形缓冲区,这样数据在读取和写入的时候都可以在这个缓冲区里循环...
  • 在C和C ++中创建循环缓冲区

    千次阅读 2021-03-12 10:02:52
    由于嵌入式系统的资源限制,在大多数项目中都可以找到循环缓冲区数据结构。 循环缓冲区(也称为环形缓冲区)是固定大小的缓冲区,其工作方式就像内存是连续的且本质上是循环的。随着内存的生成和消耗,需要重新...
  • 清空缓冲区的方法

    2010-02-01 17:30:00
    输入输出缓冲区的概念(C++用的一些) 我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标...
  • 缓冲区设计--环形队列

    万次阅读 2009-08-07 01:56:00
    在程序的两个模块间进行通讯的时候,缓冲区成为一个经常使用的机制。 如上图,写入模块将信息写入缓冲区中,读出模块将信息读出缓冲区。这样使得:将程序清晰地划分模块,建立良好的模块化架构,使得写入和读出成为...
  • 环形缓冲区 -- circular buffer

    千次阅读 2015-06-26 18:03:19
    其实很多人都搞懂环形缓冲区的本质,从而也就知道在什么场景下应该使用环形缓冲区。当你了解了它的本质你就知道这玩意儿一点都高级,而且由于它的本质在某些场景下还能使用。我看了很网络游戏服务器的源...
  • 清空输入缓冲区的方法

    千次阅读 2011-06-16 14:53:00
    输入输出缓冲区的概念(C++用的一些) 我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标...
  • Linux *中的无锁环形缓冲区 附加功能 名称 用例 环形缓冲区的解剖 单一生产者入队 单一消费者出队 个生产者入队 模数32位索引 生产者/消费者同步模式 MP / MC(默认一项) SP / SC MP_RTS / MC_RTS ...
  • 线程应用程序中使用循环缓冲区高效地进行日志记录 在关键的计算机应用程序的生存期中,日志记录是一件非常重要的活动,特别是当故障的症状并十分明显时。日志记录提供了故障前应用程序状态的详细信息,...
  • N 个生产者进程和 M 个消费者进程共享大小为 K 的缓冲区,遵循规则如下: (1) 进程之间必须以互斥方式访问缓冲区; (2) 对每 1 条放入缓冲区的数据,所有消费者都必须接收 1 次; (3) 缓冲区满时,生产者...
  • 一,JS缓冲区绘制 参考链接: https: //coderwall.com/p/zb_zdw/buffered-polyline https://coderwall.com/p/-7eojg/jsts-buffered-polyline-in-route-service-google -maps-v3 ...
  • 代码是为了使用环形缓冲区实现消费者生产者功能: package ringBuffer.parallel; import java.io.IOException; import java.io.RandomAccessFile; import java.util.LinkedHashMap; import java.util.Map; public ...
  • 前言 1.NSDictionary底层是哈希表,下面会介绍具体是用拉链法还是开放定址法线性探测来解决冲突?由于Apple给的查询复杂度可以快至O...就比如插入,如何做到尽可能少的移动或者移动插入元素后其他元素的内存?实...
  • 转载▼ *注:可以用 adb logcat > 路径/文件名 来... 后面加 > 路径/文件名 的话,则在 stdout (终端窗口)中输出! 例如:$ adb logcat -v long Checkin *:S > ~/桌面/log.txt 一、在 Java 与 C 语言中输
  • *注:可以用 adb logcat > 路径...后面加 > 路径/文件名 的话,则在 stdout (终端窗口)中输出! 例如:$ adb logcat -v long Checkin *:S > ~/桌面/log.txt 一、在 Java 与 C 语言中输出日志: 1) Java 代码在程序
  • 选择【编辑器】工具条中【编辑工具】按钮,选中其中一条分界线,使其高亮显示。选中【高级编辑】工具条中的【修剪】工具,依次点击需要裁剪移除的线,即可完成该分界线范围外的裁剪操作。若要裁剪其他范围外的要素,
  • 一、锁存器 ...锁存器不同于触发器,它在锁存数据时,输出端的信号随输入信号变化,就像信号通过一个缓冲器一样;一旦锁存信号起锁存作用,则数据被锁住,输入信号起作用。锁存器也称为透明锁存器...
  • ArcMap中3D符号化显示

    2018-12-17 21:47:43
    创建不同距离的多环缓冲区 对缓冲区进行同色带渲染并去除轮廓 3. 对缓冲区进行同色带渲染并去除轮廓结果图 4. 将符合系统转换为制图表达(制图表达必须在地理数据库中进行) 5. 在制图表达中为不同色度...
  • GDI+ 双缓冲 的起因以及解决办法

    千次阅读 2015-04-01 11:16:36
    重绘导致原因:UpdateData、Invalidate、InvalidateRect和UpdateWindow函数。 1. UpdateData重绘控件函数  UpdateData(TRUE)——刷新控件的值到对应的变量。...(变量的最终运算结果值交给外部输出显示)

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,268
精华内容 6,107
关键字:

多环缓冲区不显示