动态内存管理

(1)C/C++语言内存分配方式

C/C++定义了4个内存区间:
代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store)

<1>从静态存储区域分配.
内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在.例如全局变量、static变量.
<2>在栈上创建
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限.

<3>从堆上分配,亦称动态内存分配
程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存.动态内存的生存期由用户决定,使用非常灵活,但问题也最多.

在C++中,申请和释放堆中分配的存贮空间,分别使用new和delete的两个运算符来完成:

指针变量名=new 类型名(初始化式);
delete 指针名;
例如: int *pi=new int(0);
它与下列代码序列大体等价:
int ival=0, *pi=&ival;
区别:pi所指向的变量是由库操作符new()分配的,位于程序的堆区中,并且该对象未命名。  
堆空间申请、释放说明:
⑴.new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而且动态创建的对象本身没有名字。
⑵.一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。
⑶.堆区是不会在分配时做自动初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。

(

2)c语言内存申请相关的函数主要有alloca、calloc、malloc、realloc、free等

<1>alloca是向栈申请内存,因此无需释放.
<2>malloc分配的内存是位于堆中的,并且没有初始化内存的内容,因此基本上malloc之后,调用函数memset来初始化这部分的内存空间.
<3>calloc则将初始化这部分的内存,设置为0.
<4>realloc则对malloc申请的内存进行大小的调整.
<5>申请的内存最终需要通过函数free来释放.
当程序运行过程中malloc了,但是没有free的话,会造成内存泄漏.一部分的内存没有被使用,但是由于没有free,因此系统认为这部分内存还在使用,造成不断的向系统申请内存,使得系统可用内存不断减少.但是内存泄漏仅仅指程序在运行时,程序退出时,OS将回收所有的资源.因此,适当的重起一下程序,有时候还是有点作用.

他们之间的区别

(1)函数malloc (void* malloc(unsigned size) 在内存的动态存储区中分配一块长度为size字节的连续区域,参数size为需要内存空间的长度,返回该区域的首地址.不能初始化所分配的内存空间,而函数calloc()能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.
(2)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void*类型.void*表示未确定类型的指针.C,C++规定,void* 类型可以强制转换为任何其它类型的指针.
(3)函数calloc(void* calloc(size_t numElements, size_t sizeOfElement)参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,即在内存中申请numElements*sizeOfElement字节大小的连续地址空间. 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零.
(4)realloc(void* realloc(void* ptr, unsigned newsize)给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度.可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址.
(5)realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平;如果数据后面的字节不够,问题就出来了,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.

3) c++中内存申请相关的函数 new、delete

1. C++中,用*newdelete动态创建和释放数组或单个对象。*
动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。
int *pi=new int;
这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi

int *pi=new int(100); //指针pi所指向的对象初始化为100
string *ps=new string(10,'9');//*ps 为“9999999999”
int *pi=new int( );//初始化为0
int *pi=new int;//pi 指向一个没有初始化的int
string *ps=new string( );//初始化为空字符串 

2 撤销动态创建的对象
delete表达式释放指针指向的地址空间。
delete pi ;// 释放单个对象
delete [ ]pi;//释放数组
如果指针指向的不是new分配的内存地址,则使用delete是不合法的。

一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *pi=0;)

3 malloc/free和new/delete的区别

3.1 new 返回指定类型的指针,并且可以自动计算所需要大小。
比如:   
1) int *p;   
p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);   
或:   
int* parr;   
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;   
2) malloc 必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。   
int* p;   
p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中
double pd=(double ) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中

3.2 malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
3.3 malloc/free是C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
3.4 对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因而需要new/delete 运算符来完成动态内存分配和初始化工作与清理与释放内存工作 。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

new[] 执行原理图:operator new[] ( )——>operator new ()—->malloc(字节数)
delete[]执行原理图:取对象的个数N—->析构函数调N次—–>operator delete []—->operator delete()

4)一个类如何只在堆上(栈上)创建对象

C++中,为了让某个类只能通过new来创建(即如果直接创建对象,编译器将报错),应该(B)
A  将构造函数设为私有
B  将析构函数设为私有
C  将构造函数和析构函数都设为私有
D 没有办法可以做到

在C++中,类的创建分为两种。一种是静态创建,即直接创建对象;另一种是动态创建对象,即通过 new 创建。要想正确回答上题,就必须知道这两种创建方式的区别。
1 静态创建
由编译器在栈中为对象分配内存,通过移动栈顶指针获得合适大小的空间,然后调用对象的构造函数生成对象。
2 动态创建
通过new在堆中创建对象。这个过程分为两步:首先在堆中找到合适大小的空间并分配,然后调用对象的构造函数生成对象。

只在堆上创建对象将析构函数设为私有
类对象只能在堆上建立,就是不能静态建立类对象,即不能调用类的构造函数。
容易想到的是将构造函数设为私有,但在构造函数设为私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。
new运算符的执行过程分为两部分:第一步执行operator new()函数,在对空间中搜索适合的内存进行分配;第二步是调用构造函数构造类对象,初始化这片内存空间。
因而,当第一步执行完成后,分配了内存空间,但是无法提供构造功能,所以,这种方法不可以。
编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
测试代码:

class A
{
public:
    A(){}
    void destory(){delete this;}
private:
   ~A(){}
};

上述方法的缺点是:
(1). 无法解决继承的问题
如果A作为其他类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。可以将其设为protected,类外无法访问,子类则可以访问。
(2). 类的使用不方便
使用new来创建对象,却用destory函数来释放对象,而不是用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)为了统一,将析构函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用函数来构造,析构。

class A
{
protected:
   A(){}
  ~A(){}
public:
  static A* create()
  {
     return new A();
  }
  void destory()
  {
      delete this;
  }
};

只在栈上创建对象
只有使用new运算符,因此,只要禁用new运算符就可实现。将operator new ()设为私有即可

class A
{
private:
   void* operator new(size_t){}
   //注意函数的第一个参数和返回值都是固定的
   void operator delete(void *ptr){}
   //重载了new就需要重载delete
 public:
    A(){}
   ~A(){}
};

5) C/C++如何进行内存泄露检测

C/C++如何进行内存泄露检测

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值