内存管理这一块

发布于:2025-09-10 ⋅ 阅读:(15) ⋅ 点赞:(0)


前言

在一行一行的代码之中,不同的数据存放的位置是有所不同的,正是因为这些数据的性质不同,所以才被安排存放在不同的区域
所以,身为预备程序员,要能够分辨出不同的数据所存放的位置。


以下是本篇文章正文内容,下面案例可供参考

一、C/C++内存分布

我们先来看下面的一段代码和相关问题

int golbaval = 1;
static int staticGlobalvar = 1;
void test()
{
     static int staticval = 1;
     int localvar = 1;
     int num1[10] = {1,2,3,4};
     char char2[] = "abcd";
     const char * pchar3 = "abcd";
     int * p1 = (int *)malloc(sizeof(int)*4);
     int * p2 = (int *)calloc(4,sizeof(int));
     int * p3 = (int *)realloc(p2,sizeof(int)*4);
     free(p1);
     free(p3);
     
}

代码如上,题目如下:
选项A.栈 B.堆 C.数据段(静态区)D.代码段(常量区),下面变量分别存放在哪里?
globavar(C)
staticGlobaval(C)
staticval(C)
localvar(A)
num1(A)

——往全局中看,globavarstatGlobaval均是在全局中定义的,所以在全局中定义的,均属于静态区
——static修饰的变量不管在哪里定义都位于静态区,所以在Test函数的staticval也位于静态区
——locavar与num1都是在局部定义的变量,出了这个Test函数,两者均会被销毁,生命周期较短,所以这两者均属于

接下来,是一些比较难的。
char2(A)
*char2(A)
pchar3(A)
*pchar3(D)
p1(A)
*p1(B)

我们可以看到,char2是数组名,char2的空间其实是有5个空间,最后一个空间留给了‘\0’,我们都知道,字符串都是位于常量区的,但是char是我们在栈中定义出来的数组名,也是个变量,相当于常量区中的"abcd\0"拷贝了一份给了char2,所以char2是位于中的,*char2也是位于中的,因为是从常量区拷贝一份给了栈上的char2。
pchar3是一个指针,它指向的位置是指向位于常量区的字符串"abcd\0",pchar3是我们在栈中定义的一个指针变量,所以它位于中,*pchar3就是这块位于常量区的"abcd\0"它位于代码段(常量区)
p1是我们在栈中定义的一个指针变量,与pchar3性质相同,所以它是位于
上的一个变量,这个指针变量指向的这块空间是位于上开辟的,所以p1在上,*p1位于堆上。
在这里插入图片描述

【说明】
1.又叫堆栈-非静态成员变量/函数参数/返回值等等,栈是向下生长的。
2.用于程序运行时动态的内存分配,一般来说堆是向上身上的,也不排除有例外是向下身上。
3.数据段–存储全局数据和静态数据(像我们在全局中定义的变量以及我们使用static关键字修饰的变量)
4.代码段–可执行的代码/只读常量
在这里插入图片描述

二、C语言中动态内存管理方式

之前我们在前面的学习中就已经对动态内存开辟有了一定的了解,现在让我们回顾一下吧。
1.void* malloc(size_t size)

括号中输入的是需要向堆申请多少字节的空间,例如我们想向堆中申请5个int类型的空间,也就是20个字节,那么我们可以这样描述。(int )malloc(5sizeof(int))。

2.void * calloc(size_t n nemb,size_t size)

跟malloc类似,前面的是元素个数,后面是单个元素字节数,同样我们向堆中申请5个int类型的空间,用calloc是这样表示的,(int *)calloc(5,sizeof(int))。
注:calloc会将申请的空间全部初始化为0

3.void *realloc(void * prt,size_t size)

①realloc是用来调整之前申请的内存的空间大小,所以待调整的内存必须是之前在堆上分配的空间。
②prt是指向原先内存的指针,size是新内存字节数,返回的是指向新内存的指针,故需要强转。
③申请失败会返回NULL,所以需要检查申请是否成功。

void test()
{
    int *p2 = (int *)calloc(4,sizeof(int));
    int *p3 = (int *)realloc(p2,sizeof(int)*10);
    //这里需要free(p2)吗?
}

这里是不需要free(p2)的,p2是指向我们calloc申请的空间,p3则是指向我们新内存的空间,以上代码是对原先的空间进行扩展。

假设申请失败,则返回空指针。如果申请成功,分为两种情况。
①直接在原先的空间后面追加需要添加的空间,这种情况下,p2就跟p3相同,所以free(p3)就等同于free(p2)。
②后面的空间不允许你进行扩展,那么编译器就会向堆上重新申请一块大的空间(这块空间的大小与我们扩展后的空间大小一致),然后把旧空间上的数据拷贝到新申请的大空间里,然后释放旧空间,即此类情况,编译器会自动帮我们释放p2。

三.C++的内存管理方式

C语言的内存管理方式在C++上同时适用,但是在一些场景上C语言就显得有点力不从心,所以C++推出了属于自己的内存管理方式。通过new以及delete进行操作。

new/delete操作内置类型

void test()
{
     //申请一块int类型的空间
     int * p4 = new int;
     //申请一块int类型的空间,并将其初始化为10
     int * p5 = new int(10);
     //动态申请10个int类型的空间
     int * p6 = new int[10];
     //释放空间
     delete p4;
     delete p5;
     delete []p6;
}

这里要对应上,你是new就delete,你是new[],就delete[]。不然容易出错。

new/delete操作自定义类型

class A
{
public:
     A(int a = 0)
     :_a(a)
     {
        cout<<"A()"<<endl;
     }
     ~A()
     {
        cout<<"~A()"<<endl;
     }
}
private:
     int _a;
int main()
{
     A * p1 = malloc(sizeof(A));
     A * p2 = new int(1);
     free(p1);
     delete p2;
     //内置类型几乎没有区别
     int * p3 = (int *)malloc(sizeof(A));
     int * p4 = new int;
     free(p3);
     delete p4;
     return 0;
}
class Stack
{
public:
      Stack(int n = 4)
      :_a(new int[n])
      ,_size(0)
      _capacity(n)
      {
         cout << "Stack()"<<endl;
      }
      ~Stack()
      {
        free(_a);
        _size = _capacity = 0;
      }
private:
       int _a;
       size_t _size;
       size_t _capacity;
}
int main()
{
    Stack * p1 = (Stack *)malloc(sizeof(A);
    Stack * p2 = new Stack(10);
    free(p1);
    delete p2;
}

直接上结论:new/delete与malloc/free之间的区别是,前者new的过程中会调用构造函数,delete会调用析构,而后者没有执行此操作。
★new/delete的本质

new和delete相比于malloc/free而言,new会在malloc的基础上多了一次构造函数,delete会在free清理资源(也就是析构函数)的基础上,再次释放对象。这里我画个图,以Stack这个类举例
在这里插入图片描述

由图可知,在栈这个类里面,new会先对栈对象的整体大小进行一个计算,开出一个能够存放栈三个成员变量的空间,然后再调用构造函数对这个对象进行初始化。最后来到了delete,delete先把向堆上申请的空间资源进行一个清理,再对我们这个对象空间给释放。
简单讲,new就是operator new + 构造,delete就是operator delete + 析构。

new原理:
operator new函数申请空间。
在申请的空间上执行构造函数,完成对象的构造。
delete原理:
在空间上执行析构函数,完成对象中资源的清理工作。
调用operator delete函数释放对象的空间。
注意:new在创建对象的时候,创建失败要抛异常。

四.定位new

定位new是一种特殊的new,前面我们已经学过了普通的new,现在跟着我来看看这两者的区别吧。
简单一句话,普通new就是创建对象+构造,定位new就只有构造。

int main()
{
    A * p1 = (A*)malloc(sizeof(A));
    //这里p1只是指向了这块空间,还不能算作是关于A的对象,因为它还没有初始化。
    new(p1)A;//这一步就是对A的一个构造函数,此处也可传参参与构造
    p1->~A();//需手动进行析构清理资源(对象自身管理的资源)
    free(p1);//释放对象本身所在的内存块
    //下面案例更加具体
    A * p2 = (A*)operator new(sizeof(A));//operator new参与创建对象本身的空间
    new(p2)A(10);//这里使用定位new并且传参对对象进行构造
    p2->~A();
    free(p2);
}

在这里插入图片描述

总结

本文对内存管理进行了一个简单的讲解。new/delete相比于我们之前传统的内存管理方法要更方便,这里鼓励大家多使用new/delete进行内存的一个开发。


网站公告

今日签到

点亮在社区的每一天
去签到