5. c++ 内存管理 C/C++ 内存机制
参考自:http://blog.csdn.net/wpf_ml/article/details/7759911
1. 内存,Cache,寄存器
内存:通常计算机将数据存放在物理内存,cache及寄存器中。与其它两个数据存储方式相比,内存是比较大的。每个内存单元是通过内存地址来访问的并且内存单元是必须是联系的。不同的体系架构内存管理方式不同,在一些系统架构中部分内存用业访问物理设备(这种方式叫做内存映射I/O)。
Cache: 是一个小型的内存,一级Cache直接存放在CPU中,或是二级Cache存放在主板上。Cache存放内存中最近使用过的一个拷贝,使其加载速度更快。因为Cache被硬件隐藏我们通常不用担心Cache除非我们修改内核。
寄存器单元:位于CPU内部所以访问速度非常快.寄存器的访问速度比内存快的多,通常用来存放比较小的计算需要的数据,如:函数局部变量或算术运算的中间值。如果register关键字来定义一个局变量,这个局部变量直接被编译器放到寄存器中,而不是内存单元中。但现在编译器会自动优化来决定哪个变量放在寄存器中。
2. 从自由存储区中审请一个新的对象,C语言用malloc函数而c++用new 操作符。内存管理可能产生的问题
内存泄露
内存泄露发生在当程序运行过程中审请了内存但没有在其不需要的时候进行释放。如果内存泄露的非常多,它将消耗所有的内存资源,因为系统需要进行页交换所以最终会使系统变的非常慢,最后我们得到内存不够的错误。查询内存泄露问题是非常艰难的,因为没有清楚的代码错误。
缓存溢出
缓存溢出是在向内存中写数据进超出了内存申请时的边界值,我们也可以称其为数据损坏(data corruption)。这种情况是非常严重的,因为在写数据越界之前这种情况是不会发生的,这种情况可能在偶而发生,当其发生时我们的程序变的非常奇怪。因为内存中我有一个错误的值。
内存没有初始化
既然c/c++允许我们创建没有初始化的变量,我们就可能读到一个没有初始化的数据。通过malloc()函数或new 操作符审请的内存没有初始化。
错误的内存管理
这种错误可能发生在对同一块内存free()了多次或是一个内存已经被释放了还进行访问,或是free()一块从来没有审请的内存块。这些问题也可能发生在用delete来代替delete[],或是错误的内存访问组合:malloc 与delete或 new 与 free
3. 注意operator new 与 operator delete 只能用来审请一个单独的对象.
为数组审请内存是通过operator new[] 析构用 operator delete[].并也要注意STL容器内存管理不是直接由new与delete管理
下面的内容转自:http://www.cnblogs.com/ComputerG/archive/2012/02/01/2334898.html
一:C语言中的内存机制
在C语言中,内存主要分为如下5个存储区:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)堆(Heap):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。
(3)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。并且在C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了)。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(4)C风格字符串常量存储区: 专门存放字符串常量的地方,程序结束时释放。
(5)程序代码区:存放程序二进制代码的区域。
二:C++中的内存机制
在C++语言中,与C类似,不过也有所不同,内存主要分为如下5个存储区:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)堆(Heap):这里与C不同的是,该堆是由new申请的内存,由delete或delete[]负责释放
(3)自由存储区(Free Storage):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。
(4)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了初始化变量和未初始化变量了。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(5)常量存储区: 这是一块比较特殊的存储区,专门存储不能修改的常量(如果采用非正常手段更改当然也是可以的了)。
三:堆和栈的区别
3.1 栈(Stack)
具体的讲,现代计算机(冯诺依曼串行执行机制),都直接在代码低层支持栈的数据结构。这体现在有专门的寄存器指向栈所在的地址(SS,堆栈段寄存器,存放堆栈段地址);有专门的机器指令完成数据入栈出栈的操作(汇编中有PUSH和POP指令)。
这种机制的特点是效率高,但支持数据的数据有限,一般是整数、指针、浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构(可以自定义栈结构支持多种数据类型)。因为栈的这种特点,对栈的使用在程序中是非常频繁的 。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址入栈,然后跳转至子程序地址的操作,而子程序的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。
C/C++中的函数自动变量就是直接使用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因,因而要避免返回栈内存和栈引用,以免内存泄露。
3.2 堆(Heap)
和栈不同的是,堆得数据结构并不是由系统(无论是机器硬件系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/calloc/realloc/free函数维护了一套内部的堆数据结构(在C++中则增加了new/delete维护)。
当程序用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间(常见内存分配算法有:首次适应算法、循环首次适应算法、最佳适应算法和最差适应算法等。os的基本内容!!)。如果没有可用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回到内部堆结构中,可能会被适当的处理(比如空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。 这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下几个原因:
(1)系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分配来说会造成浪费。
(2)系统调用申请内存可能是代价昂贵的。 系统调用可能涉及到用户态和核心态的转换。
(3)没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。
3.3 栈和堆的对比
从以上介绍中,它们有如下区别:
(1)栈是系统提供的功能,特点是快速高效,缺点是由限制,数据不灵活;
堆是函数库提供的功能,特点是灵活方便,数据适应面广,但是效率有一定降低。
(2)栈是系统数据结构,对于进程/线程是唯一的;
堆是函数库内部数据结构,不一定唯一,不同堆分配的内存无法互相操作。
(3)栈空间分静态分配和动态分配,一般由编译器完成静态分配,自动释放,栈的动态分配是不被鼓励的;
堆得分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。
(4)碎片问题
对于堆来讲,频繁的new/delete等操作势必会造成内存空间的不连续,从而造成大量的碎片,使程序的效率降低;对于栈来讲,则不会存在这个问题,因为栈是后进先出(LIFO)的队列。
(5)生长方向
堆的生长方向是向上的,也就是向这内存地址增加的方向;对于栈来讲,生长方向却是向下的,是向着内存地址减少的方向增长。
(6)分配方式
堆都是动态分配的,没有静态分配的堆;
栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配则由alloca函数进行分配,但是栈的动态分配和堆不同,它的动态分配是由编译器进行释放,无需我们手工实现。
(7)分配效率
栈是机器系统提供的数据结构,计算机在底层提供支持,分配有专门的堆栈段寄存器,入栈出栈有专门的机器指令,这些都决定了栈的高效率执行。
堆是由C/C++函数库提供的,机制比较复杂,有不同的分配算法,易产生内存碎片,需要对内存进行各种管理,效率比栈要低很多。
四:具体实例分析
例子(一)
看下面的一小段C程序,仔细体会各种内存分配机制。
int a = ; //全局初始化区,a的值为0
char *p1; //全局未初始化区(C++中则初始化为NULL)
int main()
{
int b; //b分配在栈上,整型
char s[] = "abc"; //s分配在栈上,char *类型;"abc\0"分配在栈上,运行时赋值,函数结束销毁
char *p2; //p2分配在栈上,未初始化
char *p3 = ""; //p3指向"123456"分配在字符串常量存储区的地址,编译时确定
static int c = ; //c在全局(静态)初始化区,可以多次跨函数调用而保持原值
p1 = (char *)malloc(); //p1在全局未初始化区,指向分配得来得10字节的堆区地址
p2 = (char *)malloc(); //p2指向分配得来得20字节的堆区地址
strcpy(p1, ""); //"123456"放在字符串常量存储区,编译器可能会将它与p3所指向的"123456"优化成一块
return ;
}
例子(二)
看下面的一小段代码,体会堆与栈的区别:
int foo()
{
//其余代码
int *p = new int[];
//其余代码
return ;
}
其中的语句int *p = new int[5];就包含了堆与栈。其中new关键字分配了一块堆内存,而指针p本身所占得内存为栈内存(一般4个字节表示地址)。这句话的意思是在栈内存中存放了一个指向一块堆内存的指针p。在程序中先确定在堆中分配内存的大小,然后调用new关键字分配内存,最后返回这块内存首址,放入栈中。汇编代码为:
int foo()
{
008C1520 push ebp
008C1521 mov ebp,esp
008C1523 sub esp,0D8h
008C1529 push ebx
008C152A push esi
008C152B push edi
008C152C lea edi,[ebp-0D8h]
008C1532 mov ecx,36h
008C1537 mov eax,0CCCCCCCCh
008C153C rep stos dword ptr es:[edi]
int *p = new int[];
008C153E push 14h
008C1540 call operator new[] (8C1258h)
008C1545 add esp,
008C1548 mov dword ptr [ebp-0D4h],eax
008C154E mov eax,dword ptr [ebp-0D4h]
008C1554 mov dword ptr [p],eax return ;
008C1557 xor eax,eax
}
008C1559 pop edi
008C155A pop esi
008C155B pop ebx
008C155C add esp,0D8h
008C1562 cmp ebp,esp
008C1564 call @ILT+(__RTC_CheckEsp) (8C1190h)
008C1569 mov esp,ebp
008C156B pop ebp
008C156C ret
如果需要释放内存,这里我们需要使用delete[] p,告诉编译器,我要删除的是一个数组。
例子(三) !!!!!!!!!!!!!!!!!!
看下面的一小段代码,试着找出其中的错误:
#include <iostream>
using namespace std;
int main()
{
char a[] = "Hello"; // 分配在栈上
a[] = 'X';
cout << a << endl;
char *p = "World"; // 分配在字符串常量存储区的地址
p[] = 'X';
cout << p << endl;
return ;
}
发现问题了吗?是的,字符数组a的容量是6个字符,其内容为"hello\0"。a的内容时可以改变的,比如a[0]='X',因为其是在栈上分配的,也就是在运行时确定的内容。但是指针p指向的字符串"world"分配在字符串常量存储区,内容为"world\0",常量字符串的内容时不可以修改的。从语法上来说,编译器并不觉得语句p[0]='X'有什么问题,但是在运行时则会出现"access violation"非法内存访问的问题。
以下几个函数的变化要看清楚了:
char *GetString1(void)
{
char p[] = "hello,world"; //在main中,cout<< *GetString1(), 结果:h。由于p指向第一元素的地址,main中得到的返回值不能确定其一个数组名,故当做一个char。
return p;
}
char *GetString2(void)
{
char *p = "hello,world"; //在 main中,cout<< GetString2(),结果:hello,world。由于p指向“hello,world”字符串常量区域地址
return p;
} char *GetString3(void)
{
char *p = (char *)malloc(); // 指向p所分配的堆上的内存空间。
return p;
} char *GetString4(void)
{
char *p = newchar[]; // 指向p所分配的内存空间,p本身在栈上的,p所指向的空间是堆上的。
return p;
}
附录:内存管理注意事项
【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值为NULL的内存,可以在函数入口处断言检测。
【规则2】不要忘记为数组或动态内存赋初值(比如calloc比malloc就要好),指针初始化为NULL(c++中为0)。
【规则3】避免数组或指针下标越界,特别当心发生“多1”或者"少1"的操作。
【规则4】动态内存的申请和释放必须配对,防止内存泄露,具体为malloc/calloc/realloc和free配对,new和delete以及delete[]配对。
【规则5】用free或者delete释放内存后,应立即将指针设置为NULL(C++中为0),防止产生“野指针”、"悬垂指针"。
【规则6】遇到不懂得问题及时debug,一般的虫子debug一下就灰飞烟灭了,一切bug都是浮云而已。
5. c++ 内存管理 C/C++ 内存机制的更多相关文章
- JVM原理(Java代码编译和执行的整个过程+JVM内存管理及垃圾回收机制)
转载注明出处: http://blog.csdn.net/cutesource/article/details/5904501 JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.e ...
- JVM内存管理和垃圾回收机制介绍
http://backend.blog.163.com/blog/static/20229412620128233285220/ 内存管理和垃圾回收机制是JVM最核心的两个组成部分,对其内部实 ...
- JVM内存管理 + GC垃圾回收机制
2.JVM内存管理 JVM将内存划分为6个部分:PC寄存器(也叫程序计数器).虚拟机栈.堆.方法区.运行时常量池.本地方法栈 PC寄存器(程序计数器):用于记录当前线程运行时的位置,每一个线程都有一个 ...
- V8 内存管理和垃圾回收机制总结
这篇文章主要介绍 V8 的内存管理和垃圾回收知识. V8 内存管理及垃圾回收机制浅析 由于 V8 引擎的原因,Node 在操作大内存对象时受到了一些限制,在 64 位的机器上,默认最大操作的对象大小约 ...
- 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配
垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...
- Win3内存管理之私有内存跟共享内存的申请与释放
Win3内存管理之私有内存跟共享内存的申请与释放 一丶内存简介私有内存申请 通过上一篇文章.我们理解了虚拟内存与物理内存的区别. 那么我们有API事专门申请虚拟内存与物理内存的. 有私有内存跟共享内存 ...
- 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法
垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...
- JVM的生命周期、体系结构、内存管理和垃圾回收机制
一.JVM的生命周期 JVM实例:一个独立运行的java程序,是进程级别 JVM执行引擎:用户运行程序的线程,是JVM实例的一部分 JVM实例的诞生 当启动一个java程序时.一个JVM实例就诞生了, ...
- 内存管理概述、内存分配与释放、地址映射机制(mm_struct, vm_area_struct)、malloc/free 的实现
http://blog.csdn.net/pi9nc/article/details/23334659 注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料 ...
随机推荐
- Enterprise Architect使用教程
一.Enterprise Architect简介 Enterprise Architect是一个对于软件系统开发有着极好支持的CASE软件(Computer Aided Software Engine ...
- Java EE 7 / JAX-RS 2.0: Simple REST API Authentication & Authorization with Custom HTTP Header--reference
REST has made a lot of conveniences when it comes to implementing web services with the already avai ...
- Understanding Extension Class Loading--官方
http://docs.spring.io/spring-amqp/docs/1.3.6.RELEASE/reference/html/sample-apps.html#d4e1285 http:// ...
- Linux开发工具之Makefile(下)
二.Makefile(下) 01.make常用内嵌函数 函数调用 $(function arguments) $(wildcard PATTERN) 当前目录下匹配模式的文件 例如:src ...
- JSON 解析(门店)
package com.j1.mai.action; import java.io.BufferedReader; import java.io.IOException; import java.io ...
- bootstrap datetimepicker 时间段选择限制
<!DOCTYPE html> <html> <head> <title></title> <link href="./bo ...
- My.Ioc 代码示例——使用条件绑定和元数据(可选)构建插件树
本文旨在通过创建一棵插件树来演示条件绑定和元数据的用法. 说“插件树”也许不大妥当,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单.举例来说,如果要在 Winform ...
- POJ3185 The Water Bowls(反转法or dfs 爆搜)
POJ3185 The Water Bowls 题目大意: 奶牛有20只碗摆成一排,用鼻子顶某只碗的话,包括左右两只在内的一共三只碗会反向,现在给出碗的初始状态,问至少要用鼻子顶多少次才能使所有碗都朝 ...
- Windows Phone 使用 WriteableBitmap后台生成图片
这几天项目是遇到一个需求,需要后台把几个元素生成到一张图片上,并保存到文件中 private void cutscreen_Click(object sender, EventArgs e) { Gr ...
- 在ubuntu14.04上安装oracle java6 java7的方法
注意: Debian建议安装openjdk,在release包中已包含. oracle的java需要自己安装,安装步骤如下: 1. 首先安装java-package,安装方法:apt-get inst ...