C++学习:动态内存分配

一、C++中的动态内存分配

在C++中,使用运算符new分配的内存,称为动态内存,C语言使用malloc函数来分配动态内存。

动态内存由运算符new 和delete控制,而不是由作用域和链接性规则控制,因此可以在一个函数中分配动态内存,在另一个函数中将其释放。

与自动内存的栈不同,动态内存不是先进后出,其分配和释放的顺序仅取决于new和delete在何时,以何种方式被使用。

通常编译器使用3块独立的内存:

一块用于静态变量,也就是常说的静态存储区

一块用于自动变量,也就是常说的

一块用于动态内存,也就是常说的

关于数据的存储,可以参考文章:C++学习:数据的存储、作用域、链接-CSDN博客

虽然存储方案概念不适用于动态内存,但是适用于 用来跟踪动态内存的 自动和静态指针变量。比如下面的语句

float* p_free = new float[20];

由new分配80个字节(float占4个字节)的内存将一直保留在内存中,直到使用delete运算符将其释放。但是当包含该声明的语句块执行完毕时,p_free指针消失了,如果希望另一个函数能够使用这80个字节中的内容,则必须将其地址传递或者放回给该函数。另一方面,如果p_free的链接性申明为外部的,则文件中位于该申明后面的所有函数都可以使用它,通过在另一个文件中使用下面的声明,就可以使用该指针

extern float* p_free;

二、使用new运算符初始化

语法:

Type* pointer = new Type;

delete pointer;

为int和double类型分配存储空间并初始化;

所谓初始化就是在类型后面加上初始值,并用括号括起来。

int* pi = new int (6);//*pi初始化为6

double* pd = new double (99.99);// *pd初始化为99.99

程序实例1:变量申请

#include<stdio.h>

int main()
{
	int* p = new int; //在堆空间申请一个int类型的变量,指针p指向该变量
	*p = 5;
	*p = *p + 10;

	printf("p = %p\n", p);
	printf("p = %d\n", *p);

	delete p; //释放堆空间

	return 0;
}

输出结果:

p = 00CF4CA8
p = 15

数组申请

语法:

Type* pointer = new Type[N];

delete[] pointer;

程序实例2:数组申请

#include<stdio.h>

int main()
{
	int* p = new int[10]; //指向一片堆空间,数组空间不小于40个字节,而不一定是40个字节
	for (int i = 0; i < 10; i++)
	{
		p[i] = i + 1;
		printf("p[%d] = %d\n",i,p[i]);
	}

	delete[] p; //释放堆空间

	return 0;
}

输出结果

p[0] = 1
p[1] = 2
p[2] = 3
p[3] = 4
p[4] = 5
p[5] = 6
p[6] = 7
p[7] = 8
p[8] = 9
p[9] = 10

三、new与new[]分别对应的函数

new和new[]是运算符,他们分别对应下列函数

//new对应的函数
void* operator new(std::size_t);

//new[]对应的函数
void* operator new[] (std::size_t);

//delete对应的函数
void* operator delete(void*);

//delete[]对应的函数
void* operator delete[] (void*);

四、new关键字与malloc函数的区别

        new关键字是C++的一部分;

        malloc是由C库提供的函数;

        new以具体类型为单位进行内存分配;

        malloc以字节为单位进行内存分配;

        new在申请单个类型变量时可进行初始化;

        malloc不具备内存初始化的特性;

程序实例3:new关键字的初始化

#include<stdio.h>

int main()
{
	int*	pi = new int(1); //申请一段内存,int类型,并初始化为1
	float*	pf = new float(2.0f); //申请一段内存,float类型,并初始化为2.0
	char* pc = new char('c'); //申请一段内存,char类型,并初始化为'c'

	printf("pi = %d\n",*pi);
	printf("pf = %f\n", *pf);
	printf("pc = %c\n", *pc);

	return 0;
}

输出结果

pi = 1
pf = 2.000000
pc = c

五、定位new运算符

通常new负责在堆中找到一个足以能够满足要求的内存块,但是这个内存块在什么位置,只能看天意了。new运算符还有另一个变体,被称为定位new运算符,它能让你在指定的位置使用内存。程序员可以使用这种特性来设置内存管理,处理需要通过特性地址进行访问的硬件或在特定位置创造对象。

要使用定位new,首先要包含new头文件,下面的代码演示了new运算符的4种用法

#include <iostream>
#include <new>

struct chaff
{
    char dross[20];
    int slag;
};

char buffer1[50];
char buffer2[500];

int main()
{
    using namespace std;

    chaff *p1, *p2;
    int *p3, *p4;

    //常规new运算符的使用
    p1 = new chaff; //在堆中申请一片内存,用来存chaff类型的结构体
    p3 = new int[20]; //在堆中申请一片内存,用来存int[20]数组

    //定位new运算符
    p2 = new (buffer1) chaff; //把chaff类型的结构体放在buffer1中
    p4 = new (buffer2) int[20];  //把int[20]类型的数组放在buffer2中

    return 0;
}

熟悉定位new运算符之后,来看看一个示例程序,使用常规new运算符和定位new运算符创建动态分配的数组,来说明常规new与定位new之间的差别。

#include <iostream>
#include <new>

const int BUF = 512;
const int N = 5;
char buffer[BUF];


int main()
{
    using namespace std;

    double *pd1, *pd2;
    int i;
    cout << "Calling new and placement new:\n";

    pd1 = new double[N]; //在堆中申请一片内存
    pd2 = new (buffer) double[N]; //使用数组buffer的内存

    for(i=0; i<N; i++)
    {
        pd2[i] = pd1[i] = 1000 + 20.0*i;
    }
    cout << "print the memory address of pd1 and buffer:\n";
    cout <<"heap:" << pd1 <<"  static: " << (void*)buffer << endl; // 打印pd1的地址和buffer的地址

    cout << "print the element memory address of pd1 and pd2:\n";
    for(i=0; i<N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << " ; " << pd2[i] << " at " << &pd2[i] << endl;
    }


    cout << "\nCalling new and placement new a second time:\n";

    double *pd3, *pd4;
    pd3 = new double[N];//在堆中找到一片新的内存
    pd4 = new (buffer) double[N]; //覆盖数组中旧的数据
    for(i=0; i<N; i++)
    {
        pd4[i] = pd3[i] = 1000 + 40.0*i;
    }

    cout << "print the element memory address of pd3 and pd4:\n";
    for(i=0; i<N; i++)
    {
        cout << pd3[i] << " at " << &pd3[i] << " ; " << pd4[i] << " at " << &pd4[i] << endl;
    }

    cout << "\nCalling new and placement new a third time:\n ";
    delete [] pd1; //释放pd1的内存

    pd1 = new double[N]; //在堆中重新申请一片内存
    pd2 = new (buffer + N * sizeof (double) ) double[N]; //使用数组buffer的内存,在不同的位置

    for(i=0; i<N; i++)
    {
        pd2[i] = pd1[i] = 1000 + 60.0*i;
    }


    cout << "print the element memory address of pd1 and pd2:\n";
    for(i=0; i<N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << " ; " << pd2[i] << " at " << &pd2[i] << endl;
    }

    delete [] pd1;
    delete [] pd3;

    return 0;
}

程序结果

Calling new and placement new:
print the memory address of pd1 and buffer:
heap:0x941ba0  static: 0x408040
print the element memory address of pd1 and pd2:
1000 at 0x941ba0 ; 1000 at 0x408040
1020 at 0x941ba8 ; 1020 at 0x408048
1040 at 0x941bb0 ; 1040 at 0x408050
1060 at 0x941bb8 ; 1060 at 0x408058
1080 at 0x941bc0 ; 1080 at 0x408060

Calling new and placement new a second time:
print the element memory address of pd3 and pd4:
1000 at 0x941980 ; 1000 at 0x408040
1040 at 0x941988 ; 1040 at 0x408048
1080 at 0x941990 ; 1080 at 0x408050
1120 at 0x941998 ; 1120 at 0x408058
1160 at 0x9419a0 ; 1160 at 0x408060

Calling new and placement new a third time:
 print the element memory address of pd1 and pd2:
1000 at 0x941ba0 ; 1000 at 0x408068
1060 at 0x941ba8 ; 1060 at 0x408070
1120 at 0x941bb0 ; 1120 at 0x408078
1180 at 0x941bb8 ; 1180 at 0x408080
1240 at 0x941bc0 ; 1240 at 0x408088

结果分析:

首先,定位new运算符确实将数组p2放在了数组buffer中,p2和buffer的地址都是0x408040

常规new将p1放在很远的地方0x941ba0,位于动态管理的堆中;

需要指出的第二点,第二个常规new运算符查找一个新的内存块,起始地址是0x941980;但是第二个定位new运算符分配与以前相同的内存块,起始地址还是0x408040,定位new运算符使用传递给他的地址,它不用识别哪些内存单元已经被使用,也不查找未使用的内存块。将这些事情交给程序员。比如在第三次使用定位new运算符的时候,提供了一个从数组buffer开头算起的偏移量,因此将分配新内存,起始地址从0x408068开始。

程序中我们只使用delete[]运算法释放了p1和p3的内存,因为它们在堆中,使用完就要释放;

而p2和p4没有被释放,因为p2和p4是放在buffer数组中的,而buffer数组所在的内存是静态内存,delete管不着。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值