精华内容
下载资源
问答
  • 软件说明书可以借用

    2013-10-04 20:42:05
    用于网络病毒查收,非常实用的资料。值得一看
  • 在选择使用哪些外设的的时候,是去更改从官方模版中拷贝过来的stm32f10x_conf.h文件的27-48行,把我们要用的外围的头文件包含进来,不用的外围的头文件注释掉,但是你是否发现我们根本没有在用户主程序文件中包含stm...
       在选择使用哪些外设的的时候,是去更改从官方模版中拷贝过来的stm32f10x_conf.h文件的27-48行,把我们要用的外围的头文件包含进来,不用的外围的头文件注释掉,但是你是否发现我们根本没有在用户主程序文件中包含stm32f10x_conf.h这个文件。那我们的修改这个文件怎么进入主程序文件的呢?答案只有一个,它包含在stm32f10x.h这个文件中,因为在用户主文件main.c中我们第一句就使用了#include "stm32f10x.h",所以应该是stm32f10x.h这个文件包含了stm32f10x_conf.h,所以是将stm32f10x_conf.h 间接的包含进了用户主程序 main.c 文件中。(其实大家去看看固件库的文件结构图就明白了确实是这样,我们推测是对的,参见下图1)
                         图1. STM32F10xxx标准外设库体系结构
    2. 有了上面的认识我们打开stm32f10x.h中按快捷键Alt+F搜索“stm32f10x_conf.h”,我们在8291-8293行看见这么几句代码:
      
      这个条件编译就是说:如果定义了USE_STDPERIPH_DRIVER,那么就包含stm32f10x_conf.h,没有定义就不包含。那不用说了,我们想使用固件库,所以就应该预定义USE_STDPERIPH_DRIVER。在stm32f10x.h中第89行去掉注释即可。
    3. 最后需要我们指定STM的容量型号,找到52行去掉注释,这里我用的是中容量产品
     
     

    转载于:https://www.cnblogs.com/yuandongtao1989/p/6682208.html

    展开全文
  • 2019独角兽企业重金招聘Python工程师标准>>> ...

    MySQL数据库一贯以高性能、高可性和易用性著称,它已经成为世界上最流行的开源数据库。大量的个人、WEB开发者、大型公司等都在其网站、关键系统、软件包中广泛使用MySQL数据库。

      通常,许多企业在部署一种产品时,安全性常常得不到应有的重视。企业最关心的是使其可以尽快地运行,企业由此也可以尽快赢利。

      但有的企业在安装MySQL时用的是默认选项,由此造成其数据不安全,且服务器也面临被入侵的风险,并有可能在短时间内就出现性能问题。下面将提供保障MySQL安全的最佳方法。

      1、避免从互联网访问MySQL数据库,确保特定主机才拥有访问特权

      直接通过本地网络之外的计算机改变生产环境中的数据库是异常危险的。有时,管理员会打开主机对数据库的访问:

      > GRANT ALL ON *.* TO 'root'@'%';

      这其实是完全放开了对root的访问。所以,把重要的操作限制给特定主机非常重要:

      > GRANT ALL ON *.* TO 'root'@'localhost';

      > GRANT ALL ON *.* TO 'root'@'myip.athome'

      > FLUSH PRIVILEGES

      此时,你仍有完全的访问,但只有指定的IP(不管其是否静态)可以访问。

      2、定期备份数据库

      任何系统都有可能发生灾难。服务器、MySQL也会崩溃,也有可能遭受入侵,数据有可能被删除。只有为最糟糕的情况做好了充分的准备,才能够在事后快速地从灾难中恢复。企业最好把备份过程作为服务器的一项日常工作。

      3、禁用或限制远程访问

      前面说过,如果使用了远程访问,要确保只有定义的主机才可以访问服务器。这一般是通过TCP wrappers、iptables或任何其它的防火墙软件或硬件实现的。

      为限制打开网络socket,管理员应当在my.cnf或my.ini的[mysqld]部分增加下面的参数:

      skip-networking

      这些文件位于windows的C:\Program Files\MySQL\MySQL Server 5.1文件夹中,或在Linux中,my.cnf位于/etc/,或位于/etc/mysql/。这行命令在MySQL启动期间,禁用了网络连接的初始 化。请注意,在这里仍可以建立与MySQL服务器的本地连接。

      另一个可行的方案是,强迫MySQL仅监听本机,方法是在my.cnf的[mysqld]部分增加下面一行:

      bind-address=127.0.0.1

      如果企业的用户从自己的机器连接到服务器或安装到另一台机器上的web服务器,你可能不太愿意禁用网络访问。此时,不妨考虑下面的有限许可访问:

      mysql> GRANT SELECT, INSERT ON mydb.* TO 'someuser'@'somehost';

      这里,你要把someuser换成用户名,把somehost换成相应的主机。

      4、设置root用户的口令并改变其登录名

      在linux中,root用户拥有对所有数据库的完全访问权。因而,在Linux的安装过程中,一定要设置root口令。当然,要改变默认的空口令,其方法如下:

      Access MySQL控制台:$ mysql -u root -p

      在MySQL控制台中执行:

      > SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new_password');

      在实际操作中,只需将上面一行的new_password换成实际的口令即可。

      在Linux控制台中更改root口令的另一种方法是使用mysqladmin工具:

      $ mysqladmin -u root password new_password

      此时,也是将上面一行的new_password换成实际的口令即可。

      当然,这是需要使用强口令来避免强力攻击。

      为了更有效地改进root用户的安全性,另一种好方法是为其改名。为此,你必须更新表用户中的mySQL数据库。在MySQL控制台中进行操作:

      > USE mysql;

      > UPDATE user SET user="another_username" WHERE user="root";

      > FLUSH PRIVILEGES;

      然后,通过Linux访问MySQL控制台就要使用新用户名了:

      $ mysql -u another_username -p

      5、移除测试(test)数据库

      在默认安装的MySQL中,匿名用户可以访问test数据库。我们可以移除任何无用的数据库,以避免在不可预料的情况下访问了数据库。因而,在MySQL控制台中,执行:

      > DROP DATABASE test;

      6、禁用LOCAL INFILE

      另一项改变是禁用”LOAD DATA LOCAL INFILE”命令,这有助于防止非授权用户访问本地文件。在PHP应用程序中发现有新的SQL注入漏洞时,这样做尤其重要。

      此外,在某些情况下,LOCAL INFILE命令可被用于访问操作系统上的其它文件(如/etc/passwd),应使用下现的命令:

      mysql> LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE table1

      更简单的方法是:

      mysql> SELECT load_file("/etc/passwd")

      为禁用LOCAL INFILE命令,应当在MySQL配置文件的[mysqld]部分增加下面的参数:

      set-variable=local-infile=0

      7、移除匿名账户和废弃的账户

      有些MySQL数据库的匿名用户的口令为空。因而,任何人都可以连接到这些数据库。可以用下面的命令进行检查:

      mysql> select * from mysql.user where user="";

      在安全的系统中,不会返回什么信息。另一种方法是:

      mysql> SHOW GRANTS FOR ''@'localhost';

      mysql> SHOW GRANTS FOR ''@'myhost';

      如果grants存在,那么任何人都可以访问数据库,至少可以使用默认的数据库“test”。其检查方法如下:

      shell> mysql -u blablabla

      如果要移除账户,则执行命令:

      mysql> DROP USER "";

      从MySQL的5.0版开始支持DROP USER命令。如果你使用的老版本的MySQL,你可以像下面这样移除账户:

      mysql> use mysql;

      mysql> DELETE FROM user WHERE user="";

      mysql> flush privileges;

      8、降低系统特权

      常见的数据库安全建议都有“降低给各方的特权”这一说法。对于MySQL也是如此。一般情况下,开发人员会使用最大的许可,不像安全管理一样考虑许可原则,而这样做会将数据库暴露在巨大的风险中。

      为保护数据库,务必保证真正存储MySQL数据库的文件目录是由”mysql” 用户和” mysql”组所拥有的。

      shell>ls -l /var/lib/mysql

      此外,还要确保仅有用户”mysql”和root用户可以访问/var/lib/mysql目录。

      Mysql的二进制文件存在于/usr/bin/目录中,它应当由root用户或特定的”mysql”用户所拥有。对这些文件,其它用户不应当拥有“写”的访问权:

      shell>ls -l /usr/bin/my*

      9、降低用户的数据库特权

      有些应用程序是通过一个特定数据库表的用户名和口令连接到MySQL的,安全人员不应当给予这个用户完全的访问权。

      如果攻击者获得了这个拥有完全访问权的用户,他也就拥有了所有的数据库。查看一个用户许可的方法是在MySQL控制台中使用命令SHOW GRANT

      >SHOW GRANTS FOR 'user'@'localhost';

      为定义用户的访问权,使用GRANT命令。在下面的例子中,user1仅能从dianshang数据库的billing表中选择:

      > GRANT SELECT ON billing.dianshang TO 'user1'@'localhost';

      > FLUSH PRIVILEGES;

      如此一来,user1用户就无法改变数据库中这个表和其它表的任何数据。

      另一方面,如果你要从一个用户移除访问权,就应使用一个与GRANT命令类似的REVOKE命令:

      > REVOKE SELECT ON billing.ecommerce FROM 'user1'@'localhost';

      > FLUSH PRIVILEGES;

      10、移除和禁用.mysql_history文件

      在用户访问MySQL控制台时,所有的命令历史都被记录在~/.mysql_history中。如果攻击者访问这个文件,他就可以知道数据库的结构。

      $ cat ~/.mysql_history

      为了移除和禁用这个文件,应将日志发送到/dev/null。

      $export MYSQL_HISTFILE=/dev/null

      上述命令使所有的日志文件都定向到/dev/null,你应当从home文件夹移除.mysql_history:$ rm ~/.mysql_history,并创建一个到/dev/null的符号链接。

      11、安全补丁

      务必保持数据库为最新版本。因为攻击者可以利用上一个版本的已知漏洞来访问企业的数据库。

      12、启用日志

      如果你的数据库服务器并不执行任何查询,建议你启用跟踪记录,你可以通过在/etc/my.cnf文件的[Mysql]部分添加:log =/var/log/mylogfile。

      对于生产环境中任务繁重的MySQL数据库,因为这会引起服务器的高昂成本。

      此外,还要保证只有root和mysql可以访问这些日志文件。

      错误日志

      务必确保只有root和mysql可以访问hostname.err日志文件。该文件存放在mysql数据历史中。该文件包含着非常敏感的信 息,如口令、地址、表名、存储过程名、代码等,它可被用于信息收集,并且在某些情况下,还可以向攻击者提供利用数据库漏洞的信息。攻击者还可以知道安装数 据库的机器或内部的数据。

      MySQL日志

      确保只有root和mysql可以访问logfileXY日志文件,此文件存放在mysql的历史目录中。

      13、改变root目录

      Unix操作系统中的chroot可以改变当前正在运行的进程及其子进程的root目录。重新获得另一个目录root权限的程序无法访问或命名此目录之外的文件,此目录被称为“chroot监狱”。

      通过利用chroot环境,你可以限制MySQL进程及其子进程的写操作,增加服务器的安全性。

      你要保证chroot环境的一个专用目录,如/chroot/mysql。此外,为了方便利用数据库的管理工具,你可以在MySQL配置文件的[client]部分改变下面的参数:

      [client]

      socket = /chroot/mysql/tmp/mysql.sock

      14、禁用LOCAL INFILE命令

      LOAD DATA LOCAL INFILE可以从文件系统中读取文件,并显示在屏幕中或保存在数据库中。如果攻击者能够从应用程序找到SQL注入漏洞,这个命令就相当危险了。下面的命令可以从MySQL控制台进行操作:

      > SELECT LOAD_FILE("/etc/passwd");

      该命令列示了所有的用户。解决此问题的最佳方法是在MySQL配置中禁用它,在CentOS中找到/etc/my.cnf或在Ubuntu中找 到/etc/mysql/my.cnf,在[mysqld]部分增加下面一行:set-variable=local-infile=0。搞定。

      当然,唇亡齿寒,保护服务器的安全对于保障MySQL数据库的安全也是至关重要的。服务器的安全对于数据库来说可谓生死攸关。

    转载于:https://my.oschina.net/lwhmdj0823/blog/691280

    展开全文
  • <ul><li>第16页</li><li>2.3.3 所有权与借用 代码清单2-5:所有权转移</li></ul> 代码 具体代码请点击;stable&mode=debug&edition=2018&code=fn%20main%28%29%20%7B%0A%20%20%20%20%2F%2...
  • 文章为转载他人 + 自己补充 一、预备知识—程序的内存分配   一个由C/C++编译的程序占用的内存分为以下几个部分   1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。...
    
    

    文章为转载他人 + 自己补充   非常经典,值得与众人分享

    以下是C语言中的区别

    一、预备知识—程序的内存分配  
      一个由C/C++编译的程序占用的内存分为以下几个部分  
      1、栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等。其  
      操作方式类似于数据结构中的栈。  (补充:局部变量, 生命周期外自动被系统回收
      2、堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回  
      收   。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。  (补充:malloc、alloc出的空间,必须手动释放
      3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的  
      全局变量和静态变量在一块区域,   未初始化的全局变量和未初始化的静态变量在相邻的另  
      一块区域。   -   程序结束后由系统释放。  (补充:static 关键字修饰的变量 该区上的数据在应用程序的整个生命周期中一直存在 只有当程序退出时才会被系统回收
      4、文字常量区   —常量字符串就是放在这里的。   程序结束后由系统释放  
      5、程序代码区—存放函数体的二进制代码。  
     

    //半路插入IOS中的理解

    操作系统iOS 中应用程序使用的计算机内存不是统一分配空间,运行代码使用的空间在三个不同的内存区域,分成三个段:“text segment “,“stack segment ”,“heap segment ”。

    段“text segment ”是应用程序运行时应用程序代码存在的内存段。每一个指令,每一个单个函数、过程、方法和执行代码都存在这个内存段中直到应用程序退出。一般情况下,你不会真的不得不知道这个段的任何事情。

    当应用开始以后,函数main() 被调用,一些空间分配在”stack” 中。这是为应用分配的另一个段的内存空间,这是为了函数变量存储需要而分配的内存。每一次在应用中调用一个函数,“stack ”的一部分会被分配在”stack” 中,称之为”frame” 。新函数的本地变量分配在这里。

    正如名称所示,“stack ”是后进先出(LIFO )结构。当函数调用其他的函数时,“stack frame ”会被创建;当其他函数退出后,这个“frame ”会自动被破坏。

     “heap” 段也称为”data” 段,提供一个保存中介贯穿函数的执行过程,全局和静态变量保存在“heap”中,直到应用退出。

    为了访问你创建在heap 中的数据,你最少要求有一个保存在stack 中的指针,因为你的CPU 通过stack 中的指针访问heap 中的数据。

    你可以认为stack 中的一个指针仅仅是一个整型变量,保存了heap 中特定内存地址的数据。实际上,它有一点点复杂,但这是它的基本结构。

     

    简而言之,操作系统使用stack 段中的指针值访问heap 段中的对象。如果stack 对象的指针没有了,则heap 中的对象就不能访问。这也是内存泄露的原因。

    在iOS 操作系统的stack 段和heap 段中,你都可以创建数据对象。

    stack 对象的优点主要有两点,一是创建速度快,二是管理简单,它有严格的生命周期。stack 对象的缺点是它不灵活。创建时长度是多大就一直是多大,创建时是哪个函数创建的,它的owner 就一直是它。不像heap 对象那样有多个owner ,其实多个owner 等同于引用计数。只有heap 对象才是采用“引用计数”方法管理它。

    stack 对象的创建

    只要栈的剩余空间大于stack 对象申请创建的空间,操作系统就会为程序提供这段内存空间,否则将报异常提示栈溢出。

    heap 对象的创建

    操作系统对于内存heap 段是采用链表进行管理的。操作系统有一个记录空闲内存地址的链表,当收到程序的申请时,会遍历链表,寻找第一个空间大于所申请的heap 节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。

    例如:

    NSString 的对象就是stack 中的对象,NSMutableString 的对象就是heap 中的对象。前者创建时分配的内存长度固定且不可修改;后者是分配内存长度是可变的,可有多个owner, 适用于计数管理内存管理模式。

     

    两类对象的创建方法也不同,前者直接创建“NSString * str1=@"welcome"; “,而后者需要先分配再初始化“ NSMutableString * mstr1=[[NSMutableString alloc] initWithString:@"welcome"]; ”。

     

    再补充一点,这里说的是操作系统的堆和栈。

    在我们学习“数据结构”时,接触到的堆和栈的概念和这个操作系统中的堆和栈不是一回事的。

    操作系统的堆和栈是指对内存进行操作和管理的一些方式。

    “数据结构“的堆实际上指的就是(满足堆性质的)优先Queue 的一种数据结构,第1 个元素有最高的优先权;栈实际上就是满足先进后出的性质的数据或数据结构。

    //插入结束  
       
      二、例子程序    
      这是一个前辈写的,非常详细    
      //main.cpp    
      int   a   =   0;   全局初始化区    
      char   *p1;   全局未初始化区    
      main()    
      {    
      int   b;   栈    
      char   s[]   =   "abc";   栈    
      char   *p2;   栈    
      char   *p3   =   "123456";   123456/0在常量区,p3在栈上。    
      static   int   c   =0;   全局(静态)初始化区    
      p1   =   (char   *)malloc(10);    
      p2   =   (char   *)malloc(20);    
      分配得来得10和20字节的区域就在堆区。    
      strcpy(p1,   "123456");   123456/0放在常量区,编译器可能会将它与p3所指向的"123456"  
      优化成一个地方。    
      }    
       
       
      二、堆和栈的理论知识    
      2.1申请方式    
      stack:    
      由系统自动分配。   例如,声明在函数中一个局部变量   int   b;   系统自动在栈中为b开辟空  
      间    
      heap:    
      需要程序员自己申请,并指明大小,在c中malloc函数    
      如p1   =   (char   *)malloc(10);    
      在C++中用new运算符    
      如p2   =   new   char[10];    
      但是注意p1、p2本身是在栈中的。    
       
       
      2.2    
      申请后系统的响应    
      栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢  
      出。    
      堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,  
      会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表  
      中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的  
      首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。  
      另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部  
      分重新放入空闲链表中。    
       
      2.3申请大小的限制    
      栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意  
      思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有  
      的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将  
      提示overflow。因此,能从栈获得的空间较小。    
      堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储  
      的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小  
      受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。    
       
       
       
      2.4申请效率的比较:    
      栈由系统自动分配,速度较快。但程序员是无法控制的。    
      堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.    
      另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是  
      直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。  
         
       
      2.5堆和栈中的存储内容    
      栈:   在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可  
      执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈  
      的,然后是函数中的局部变量。注意静态变量是不入栈的。    
      当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地  
      址,也就是主函数中的下一条指令,程序由该点继续运行。    
      堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。    
       
      2.6存取效率的比较    
       
      char   s1[]   =   "aaaaaaaaaaaaaaa";    
      char   *s2   =   "bbbbbbbbbbbbbbbbb";    
      aaaaaaaaaaa是在运行时刻赋值的;    
      而bbbbbbbbbbb是在编译时就确定的;    
      但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。    
      比如:    
      #include    
      void   main()    
      {    
      char   a   =   1;    
      char   c[]   =   "1234567890";    
      char   *p   ="1234567890";    
      a   =   c[1];    
      a   =   p[1];    
      return;    
      }    
      对应的汇编代码    
      10:   a   =   c[1];    
      00401067   8A   4D   F1   mov   cl,byte   ptr   [ebp-0Fh]    
      0040106A   88   4D   FC   mov   byte   ptr   [ebp-4],cl    
      11:   a   =   p[1];    
      0040106D   8B   55   EC   mov   edx,dword   ptr   [ebp-14h]    
      00401070   8A   42   01   mov   al,byte   ptr   [edx+1]    
      00401073   88   45   FC   mov   byte   ptr   [ebp-4],al    
      第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到  
      edx中,再根据edx读取字符,显然慢了。    
       
       
      2.7小结:    
      堆和栈的区别可以用如下的比喻来看出:    
      使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就  
      走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自  
      由度小。    
      使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由  
      度大。   (经典!)  

    windows进程中的内存结构


    在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。 

    接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。 

    首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码: 

    #include <stdio.h> 

    int g1=0, g2=0, g3=0; 

    int main() 

    static int s1=0, s2=0, s3=0; 
    int v1=0, v2=0, v3=0; 

    //打印出各个变量的内存地址 

    printf("0x%08x\n",&v1); //打印各本地变量的内存地址 
    printf("0x%08x\n",&v2); 
    printf("0x%08x\n\n",&v3); 
    printf("0x%08x\n",&g1); //打印各全局变量的内存地址 
    printf("0x%08x\n",&g2); 
    printf("0x%08x\n\n",&g3); 
    printf("0x%08x\n",&s1); //打印各静态变量的内存地址 
    printf("0x%08x\n",&s2); 
    printf("0x%08x\n\n",&s3); 
    return 0; 


    编译后的执行结果是: 

    0x0012ff78 
    0x0012ff7c 
    0x0012ff80 

    0x004068d0 
    0x004068d4 
    0x004068d8 

    0x004068dc 
    0x004068e0 
    0x004068e4 

    输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。 


    ├———————┤低端内存区域 
    │ …… │ 
    ├———————┤ 
    │ 动态数据区 │ 
    ├———————┤ 
    │ …… │ 
    ├———————┤ 
    │ 代码区 │ 
    ├———————┤ 
    │ 静态数据区 │ 
    ├———————┤ 
    │ …… │ 
    ├———————┤高端内存区域 


    堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码: 

    #include <stdio.h> 

    void __stdcall func(int param1,int param2,int param3) 

    int var1=param1; 
    int var2=param2; 
    int var3=param3; 
    printf("0x%08x\n",¶m1); //打印出各个变量的内存地址 
    printf("0x%08x\n",¶m2); 
    printf("0x%08x\n\n",¶m3); 
    printf("0x%08x\n",&var1); 
    printf("0x%08x\n",&var2); 
    printf("0x%08x\n\n",&var3); 
    return; 


    int main() 

    func(1,2,3); 
    return 0; 


    编译后的执行结果是: 

    0x0012ff78 
    0x0012ff7c 
    0x0012ff80 

    0x0012ff68 
    0x0012ff6c 
    0x0012ff70 


    ├———————┤<—函数执行时的栈顶(ESP)、低端内存区域 
    │ …… │ 
    ├———————┤ 
    │ var 1 │ 
    ├———————┤ 
    │ var 2 │ 
    ├———————┤ 
    │ var 3 │ 
    ├———————┤ 
    │ RET │ 
    ├———————┤<—“__cdecl”函数返回后的栈顶(ESP) 
    │ parameter 1 │ 
    ├———————┤ 
    │ parameter 2 │ 
    ├———————┤ 
    │ parameter 3 │ 
    ├———————┤<—“__stdcall”函数返回后的栈顶(ESP) 
    │ …… │ 
    ├———————┤<—栈底(基地址 EBP)、高端内存区域 


    上图就是函数调用过程中堆栈的样子了。首先,三个参数以从又到左的次序压入堆栈,先压“param3”,再压“param2”,最后压入“param1”;然后压入函数的返回地址(RET),接着跳转到函数地址接着执行(这里要补充一点,介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后,继续压入当前EBP,然后用当前ESP代替EBP。然而,有一篇介绍windows下函数调用的文章中说,在windows下的函数调用也有这一步骤,但根据我的实际调试,并未发现这一步,这还可以从param3和var1之间只有4字节的间隙这点看出来);第三步,将栈顶(ESP)减去一个数,为本地变量分配内存空间,上例中是减去12字节(ESP=ESP-3*4,每个int变量占用4个字节);接着就初始化本地变量的内存空间。由于“__stdcall”调用由被调函数调整堆栈,所以在函数返回前要恢复堆栈,先回收本地变量占用的内存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前压入参数占用的内存(ESP=ESP+3*4),继续执行调用者的代码。参见下列汇编代码: 

    ;--------------func 函数的汇编代码------------------- 

    :00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间 
    :00401003 8B442410 mov eax, dword ptr [esp+10] 
    :00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
    :0040100B 8B542418 mov edx, dword ptr [esp+18] 
    :0040100F 89442400 mov dword ptr [esp], eax 
    :00401013 8D442410 lea eax, dword ptr [esp+10] 
    :00401017 894C2404 mov dword ptr [esp+04], ecx 

    ……………………(省略若干代码) 

    :00401075 83C43C add esp, 0000003C ;恢复堆栈,回收本地变量的内存空间 
    :00401078 C3 ret 000C ;函数返回,恢复参数占用的内存空间 
    ;如果是“__cdecl”的话,这里是“ret”,堆栈将由调用者恢复 

    ;-------------------函数结束------------------------- 


    ;--------------主程序调用func函数的代码-------------- 

    :00401080 6A03 push 00000003 //压入参数param3 
    :00401082 6A02 push 00000002 //压入参数param2 
    :00401084 6A01 push 00000001 //压入参数param1 
    :00401086 E875FFFFFF call 00401000 //调用func函数 
    ;如果是“__cdecl”的话,将在这里恢复堆栈,“add esp, 0000000C” 

    聪明的读者看到这里,差不多就明白缓冲溢出的原理了。先来看下面的代码: 

    #include <stdio.h> 
    #include <string.h> 

    void __stdcall func() 

    char lpBuff[8]="\0"; 
    strcat(lpBuff,"AAAAAAAAAAA"); 
    return; 


    int main() 

    func(); 
    return 0; 


    编译后执行一下回怎么样?哈,“"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。”,“非法操作”喽!"41"就是"A"的16进制的ASCII码了,那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节,算进结尾的\0,那strcat最多只能写入7个"A",但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图,多出来的4个字节正好覆盖了RET的所在的内存空间,导致函数返回到一个错误的内存地址,执行了错误的指令。如果能精心构造这个字符串,使它分成三部分,前一部份仅仅是填充的无意义数据以达到溢出的目的,接着是一个覆盖RET的数据,紧接着是一段shellcode,那只要着个RET地址能指向这段shellcode的第一个指令,那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置,那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令,使得exploit有更强的通用性。 


    ├———————┤<—低端内存区域 
    │ …… │ 
    ├———————┤<—由exploit填入数据的开始 
    │ │ 
    │ buffer │<—填入无用的数据 
    │ │ 
    ├———————┤ 
    │ RET │<—指向shellcode,或NOP指令的范围 
    ├———————┤ 
    │ NOP │ 
    │ …… │<—填入的NOP指令,是RET可指向的范围 
    │ NOP │ 
    ├———————┤ 
    │ │ 
    │ shellcode │ 
    │ │ 
    ├———————┤<—由exploit填入数据的结束 
    │ …… │ 
    ├———————┤<—高端内存区域 


    windows下的动态数据除了可存放在栈中,还可以存放在堆中。了解C++的朋友都知道,C++可以使用new关键字来动态分配内存。来看下面的C++代码: 

    #include <stdio.h> 
    #include <iostream.h> 
    #include <windows.h> 

    void func() 

    char *buffer=new char[128]; 
    char bufflocal[128]; 
    static char buffstatic[128]; 
    printf("0x%08x\n",buffer); //打印堆中变量的内存地址 
    printf("0x%08x\n",bufflocal); //打印本地变量的内存地址 
    printf("0x%08x\n",buffstatic); //打印静态变量的内存地址 


    void main() 

    func(); 
    return; 


    程序执行结果为: 

    0x004107d0 
    0x0012ff04 
    0x004068c0 

    可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC编译器是通过windows下的“堆(heap)”来实现new关键字的内存动态分配。在讲“堆”之前,先来了解一下和“堆”有关的几个API函数: 

    HeapAlloc 在堆中申请内存空间 
    HeapCreate 创建一个新的堆对象 
    HeapDestroy 销毁一个堆对象 
    HeapFree 释放申请的内存 
    HeapWalk 枚举堆对象的所有内存块 
    GetProcessHeap 取得进程的默认堆对象 
    GetProcessHeaps 取得进程所有的堆对象 
    LocalAlloc 
    GlobalAlloc 

    当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间: 

    HANDLE hHeap=GetProcessHeap(); 
    char *buff=HeapAlloc(hHeap,0,8); 

    其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧: 

    #pragma comment(linker,"/entry:main") //定义程序的入口 
    #include <windows.h> 

    _CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf 
    /*--------------------------------------------------------------------------- 
    写到这里,我们顺便来复习一下前面所讲的知识: 
    (*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。 
    由函数定义可见,printf的参数个数是可变的,函数内部无法预先知道调用者压入的参数个数,函数只能通过分析第一个参数字符串的格式来获得压入参数的信息,由于这里参数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是__stdcall调用形式,只有一个API例外,那就是wsprintf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个数是可变的缘故。 
    ---------------------------------------------------------------------------*/ 
    void main() 

    HANDLE hHeap=GetProcessHeap(); 
    char *buff=HeapAlloc(hHeap,0,0x10); 
    char *buff2=HeapAlloc(hHeap,0,0x10); 
    HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); 
    printf=(void *)GetProcAddress(hMsvcrt,"printf"); 
    printf("0x%08x\n",hHeap); 
    printf("0x%08x\n",buff); 
    printf("0x%08x\n\n",buff2); 


    执行结果为: 

    0x00130000 
    0x00133100 
    0x00133118 

    hHeap的值怎么和那个buff的值那么接近呢?其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构,这个结构中存放着一些有关进程的重要信息,其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址,而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的,同一时刻只能有一个线程访问堆中的数据,当多个线程同时有访问要求时,只能排队等待,这样便造成程序执行效率下降。 

    最后来说说内存中的数据对齐。所位数据对齐,是指数据所在的内存地址必须是该数据长度的整数倍,DWORD数据的内存起始地址能被4除尽,WORD数据的内存起始地址能被2除尽,x86 CPU能直接访问对齐的数据,当他试图访问一个未对齐的数据时,会在内部进行一系列的调整,这些调整对于程序来说是透明的,但是会降低运行速度,所以编译器在编译程序时会尽量保证数据对齐。同样一段代码,我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果: 

    #include <stdio.h> 

    int main() 

    int a; 
    char b; 
    int c; 
    printf("0x%08x\n",&a); 
    printf("0x%08x\n",&b); 
    printf("0x%08x\n",&c); 
    return 0; 


    这是用VC编译后的执行结果: 
    0x0012ff7c 
    0x0012ff7b 
    0x0012ff80 
    变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。 

    这是用Dev-C++编译后的执行结果: 
    0x0022ff7c 
    0x0022ff7b 
    0x0022ff74 
    变量在内存中的顺序:c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。 

    这是用lcc编译后的执行结果: 
    0x0012ff6c 
    0x0012ff6b 
    0x0012ff64 
    变量在内存中的顺序:同上。 

    三个编译器都做到了数据对齐,但是后两个编译器显然没VC“聪明”,让一个char占了4字节,浪费内存哦。 


    基础知识: 
    堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针,EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。 


    参考:《Windows下的HEAP溢出及其利用》by: isno 
    《windows核心编程》by: Jeffrey Richter 





    摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)

    前言
    您是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了“自动化”?您的程序是否因堆分配而运行起来很慢?不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。

    什么是堆?
    (如果您已经知道什么是堆,可以跳到“什么是常见的堆性能问题?”部分)

    在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作: 

    事先不知道程序所需对象的数量和大小。


    对象太大而不适合堆栈分配程序。
    堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。


    GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。

    LocalAlloc/LocalFree:Win32 堆调用(为了与 Microsoft Windows NT 兼容),这些调用直接与每个进程的默认堆进行对话。

    COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用“组件对象模型 (COM)”的分配程序,而申请的程序使用每个进程堆。

    C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留在 Win32 堆的顶部。

    Windows NT 中,Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。

    Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。

    在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。

    分配和释放块不就那么简单吗?为何花费这么长时间?

    堆实现的注意事项
    传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”。如果没有其他堆可使用,则块的分配使用“进程堆”。语言运行时也能在进程内创建单独的堆。(例如,C 运行时创建它自己的堆。)除这些专用的堆外,应用程序或许多已载入的动态链接库 (DLL) 之一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。

    当应用程序或 DLL 创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)

    在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。

    典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。

    Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理内存” (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实现和设计的详细讨论也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

    Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个“大块”列表。“大块”列表(空闲列表[0]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,“进程堆”执行收集操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。

    单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Workshop”上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。

    什么是常见的堆性能问题?
    以下是您使用堆时会遇到的最常见问题: 

    分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。


    释放操作造成的速度减慢。释放操作耗费较多周期,主要是启用了收集操作。收集期间,每个释放操作“查找”它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表。在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低。


    堆竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。 
    竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。

    堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题。(破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节,请参见 Microsoft Visual C++(R) 调试文档 。)


    频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配,随重分配增长和释放。不要这样做,如果可能,尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。
    竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。

    在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。

    尽量减少堆的使用
    现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略,情况将会大大好转。调整使用堆的方法,减少对堆的操作是提高性能的良方。

    如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例:

    struct ObjectA {
       // objectA 的数据 
    }

    struct ObjectB {
       // objectB 的数据 
    }

    // 同时使用 objectA 和 objectB

    //
    // 使用指针 
    //
    struct ObjectB {
       struct ObjectA * pObjA;
       // objectB 的数据 
    }

    //
    // 使用嵌入
    //
    struct ObjectB {
       struct ObjectA pObjA;
       // objectB 的数据 
    }

    //
    // 集合 – 在另一对象内使用 objectA 和 objectB
    //

    struct ObjectX {
       struct ObjectA  objA;
       struct ObjectB  objB;
    }

    避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。


    把带指针的子对象嵌入父对象。当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。


    合并小对象形成大对象(聚合)。聚合减少分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。


    内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针。这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能。


    在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪,例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。 
    块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。

    正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。
    使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。

    其他提高性能的技术
    下面是一些提高速度的技术: 

    使用 Windows NT5 堆 
    由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:

    改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。


    使用 “Lookaside”列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节(以 8-字节递增)的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。


    内部数据结构算法也得到改进。
    这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每个进程堆的通用机制。如果希望获得高的局部性能,则使用 Heap(R) API 来存取每个进程堆,或为分配操作创建自己的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

    上述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)

    使用分配高速缓存 
    分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。

    典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息”。 

    与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。

    需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。

    可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。

    分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。

    MP 堆 
    MP 堆是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。

    本程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。

    重新思考算法和数据结构 
    要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。

    减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。

    如果大量使用“Automation”结构,请考虑从主线代码中删除“Automation BSTR”,或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。)

    摘要
    对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。 

    评价您的代码中堆的使用。


    改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。


    在实现自定义的包装程序之前使用量化堆调用成本的方法。


    如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。


    要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。


    操作系统(Windows NT 家族)正在不断改进堆。请随时关注和利用这些改进。
    Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。工作之外,他喜欢阅读、打排球和家庭烹饪。



    http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
    我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中,如下  
    CObject  object;  
    还有一种是在堆(heap)中  如下  
    CObject*  pobject=new  CObject();  

    请问  
    (1)这两种方式有什么区别?  
    (2)堆栈与堆有什么区别??  


    ---------------------------------------------------------------  

    1)  about  stack,  system  will  allocate  memory  to  the  instance  of  object  automatically,  and  to  the 
    heap,  you  must  allocate  memory  to  the  instance  of  object  with  new  or  malloc  manually.  
    2)  when  function  ends,  system  will  automatically  free  the  memory  area  of  stack,  but  to  the  
    heap,  you  must  free  the  memory  area  manually  with  free  or  delete,  else  it  will  result  in  memory 
    leak.  
    3)栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。  
    4)堆上分配的内存可以有我们自己决定,使用非常灵活。  
    ---------------------------------------------------------------  

    展开全文
  • 案例:一个计算器类,实现加减乘操作。 1.普通写法 #include<iostream> #include<string> using namespace std; class calculate { int m_a; int m_b; public: calculate(int a, int b) { ... r

    先说结论:相比于普通写法,我们更推荐多态写法。

    案例:一个计算器类,实现加减乘操作。

    1.普通写法

    #include<iostream>
    #include<string>
    using namespace std;
    class calculate {
    	int m_a;
    	int m_b;
    public:
    	calculate(int a, int b) {
    		m_a = a;
    		m_b = b;
    	}
    	int function_calculate(string str) {
    		if (str == "+")
    			return m_a + m_b;
    		if (str == "-")
    			return m_a - m_b;
    		if (str == "*")
    			return m_a + m_b;
    		else
    			cout << "error" << endl;
    	}
    };
    void test01() {
    	calculate c(100, 200);
    	cout<<c.function_calculate("+");
    }
    int main() {
    	test01();
    }

    2.多态写法

    #include<iostream>
    #include<string>
    using namespace std;
    
    class abstract_calculate {
    public:
    	virtual int function() { return 0; }
    	int m_a;
    	int m_b;
    };
    
    class add_calculate:public abstract_calculate{
    public:
    	int function() {
    		return m_a + m_b;
    	}
    };
    
    class sub_calculate :public abstract_calculate {
    public:
    	int function() {
    		return m_a - m_b;
    	}
    };
    
    class mul_calculate :public abstract_calculate {
    public:
    	int function() {
    		return m_a * m_b;
    	}
    };
    //通过父类指针指向子类的方法
    void test01() {
    
    	abstract_calculate* a = new add_calculate;
    	a->m_a = 100;
    	a->m_b = 200;
    	cout<<a->function(); 
        delete a;
    }
    
    int main() {
    	test01();
    }

     概述:通过下面那张图不难发现,再在实现相同的功能时,动态写法的代码量更多,那为什么我们还要推广使用多态写法呢?

     

    多态的优点:

    • 代码组织结构清晰
    • 可读性强,逻辑层次鲜明,代码像诗一样优美。
    • 利于前期和后期的扩展以及维护,快速定位。

    在实际开发中,我们提倡开闭原则,即对扩展进行开发,对修改进行关闭。

    就拿上面那个例子来说,我们普通写法在后期维护(就比如某个功能出错了)的时候还得去计算器类里一行一行读代码找到错误的那行,多态写法则直接找到相应的类就行,比如加法错了我就找加法那个类,其他的类我就不看了,这样就能实现快速定位,尤其是在一个大工程的时候,这样写很必要。

    展开全文
  • 右边还有详细操作说明。做得非常贴心。大家可以根据自己的实际需求各取所需。 下边我抛砖引玉,介绍win10系统中最常用的几大功能。方便大家快速上手。 二、磁盘清理工具 windows的使用过程中会产生各种各样的垃圾...
  • 2009-12-16public class TestMain { public static void main(String args[]){ TestSynchronized r1 = new TestSynchronized(); TestSynchronized r2 = new TestSynchronized();
  • rust 语法和语义 10 引用和借用

    千次阅读 2018-06-06 21:19:47
    所有权概念将依照官方介绍,分为3个部分说明: 所有权 ownership 引用和借用 references and borrowing 生命周期 lifetimes 概述 操作格式 声明 example &amp;T 引用(对象不可变) ...
  • RedHatlinux 默认是安装了yum软件的,但是由于激活认证的原因让redhat无法直接进行yum安装一些软件,如果我们需要在redhat下直接yum...使用说明 (先将/etc/yum.repos.d的内容移到其他地方,将下载后的.repo复...
  • 请问一下FPGA怎么借用HPS未使用的GPIO管脚?![图片说明](https://img-ask.csdn.net/upload/201906/06/1559790510_891845.png) 在这个里面没有定义使用GPIO,我想在顶层里面使用未使用的管脚,但是在顶层使用之后...
  • 上次博客跟大家分享了自己对原型链继承的理解,想看的同学...我们举个栗子来说明下: Q1:共享的超类属性能被任何实例改写,这个是很危险的!看下面一段代码: function Person(name){ this.name=name; this....
  • 1、客户上传Word文档到服务器 2、服务器调用OpenOffice程序打开上传的Word文档 ...4、Over 至此可见,这要求服务器端安装OpenOffice软件,其实也可以是MS Office,不过...恩,说明一下,本文的测试基于 MS Win7 Ul
  • 四、关于echo的说明, 可用示例实际验证 @echo off表示执行了这条命令后关闭所有命令(包括本身这条命令)的回显。而echo off命令则表示关闭其他所有命令(不包括本身这条命令)的回显,@的作用就是关闭紧跟其后的一条...
  • 我们接下来要探讨的概念是Rust的内存安全...接下来让我们详细说明这些原则。 5.7.1 所有权 程序中资源的真正所有者的概念因语言而异。这里的含义是通过资源,我们共同引用在堆或堆栈上保存值的任何变量,或者是包含
  • Linux文件权限说明

    2021-04-26 15:32:01
    网上看到一个图,描述了Linux下文件和目录的权限参数说明,写的很详细,借用来贴一下,如下:
  • [图片说明](https://img-ask.csdn.net/upload/201603/15/1458022286_689918.png) ``` <title>Converse.js ; charset=utf-8" /> , initial-scale=1.0" /> ...
  • 从浏览器的已验证会话中借用cookie,以在Python脚本中使用。 免费软件:麻省理工学院 文档: : 安装 注意:如果您仍在使用Python 2并使用pycookiecheat <v0> = v0.4.0需要Python 3.5+,并且可能很快会升级到3.6+...
  • 如果希望使用其他应用程序的资源,如位图,icon,可以使用GetModuleHandle得到这个应用程序的实例句柄,再用LoadImage把它加载进来。代码如下: HBITMAP bmp = (HBITMAP)LoadImage...需要说明的是,path必须是
  • 从F#中借用函数化API

    2007-11-21 07:50:00
    通用语言规范(Common Language Specification,CLS)确保任何具有一致性的.NET语言能访问被...\Dustin Campbell通过演示一个能被大量简化的C#表达式是如何地简单来说明这种方式。它以如下代码开始:\int[] a = newi...
  • 这是本人asp.net上传图片的一个类源码,大家可以借用,可以正常使用,,如果代码不符合你使用的要求,可以在此基础上进行改动,希望可以共同讨论。/// /// Upload 的摘要说明/// 用于上传图片,限制文件大小、格式等...
  • 一,借用构造函数 二、事件代理 三,call和apply的用法 四、总结 零、寒暄&回顾 上次博客跟大家分享了自己对原型链继承的理解,想看的同学欢迎猛击这里,上次说到原型链继承有一些问题,主要是两方面的。...
  • 教程说明 破解win7开机密码的方式有很多,本文是方法之一,但是无论任何一种方法,都需要耐心学习和细心操作。 如果您需要急用电脑,并且用最简便的步骤,可以移步至:win7/win10/xp电脑开机密码...
  • 1、客户上传Word文档到服务器 2、服务器调用OpenOffice程序打开上传的Word文档 ...至此可见,这要求服务器端安装OpenOffice软件,其实也可以是MS Office,不过OpenOffice的优势是...恩,说明一下,本文的测试基于
  • 我们强烈建议您认真遵循以下说明。 这很多,并且需要一些时间来设置,但是从长远来看,这种方式会使您的生活变得更加轻松。 使用此存储库模板(请勿克隆) 不要分叉或克隆。 而是单击“ Use this Template按钮,...
  • 【调用方法及参数说明】: 1、在页面中引入ymPrompt.js。如: 2、在页面中引入对应的皮肤文件的CSS,如: 3、自定义组件的默认配置信息(此步骤可选,该方法可以在任意时间调用) [color=red]handler: ...
  • 1安装node.js从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以了1.1输入 node -v,如下图,如果出现相应的版本号,则说明安装成功。image.png1.2 npm包管理器,是集成在node中的,所以,直接输入...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,244
精华内容 497
关键字:

借用说明