C++内存管理

简介: C++内存管理

1. c的内存管理例题

下面这道例题用于检测c的内存管理的学习程度,又或者说是学到这里c究竟忘了多少…

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}
/*1. 选择题:
   选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
   globalVar在哪里?__C__   staticGlobalVar在哪里?__C__
   staticVar在哪里?_C___   localVar在哪里?___A_
   num1 在哪里?__A__
   char2在哪里?____   *char2在哪里?___
   pChar3在哪里?____      *pChar3在哪里?____
   ptr1在哪里?____        *ptr1在哪里?____
 - 填空题:
   sizeof(num1) = ____;  
    sizeof(char2) = ____;      strlen(char2) = ____;
   sizeof(pChar3) = ____;     strlen(pChar3) = ____;
   sizeof(ptr1) = ____;
   */
  • globalvar: 是全局变量 处于静态区
  • staticGlobalvar :是全局静态变量 ,处于静态区
  • staticvar : 是局部静态变量 ,处于静态区
  • localvar: 是局部变量 ,处于 栈
  • num1 : 是一个局部的数组,处于栈
  • char2 是一个字符数组 ,处于栈

*char2 :char2是一个数组名,由于既不单独放在sizeof内部,也没有取地址,数组名作为首元素地址,*char2是第一个元素,而整个数组处于栈中,所以 *char2处于栈

pchar3: 是一个由const修饰的字符类型指针,指针指向的内容不能改变, 说明"abcd"是一个常量字符串,内容不能被修改,处于栈

*pchar3 :由于"abcd"是一个常量字符串,pchar3指向常量字符串,*pchar3 处于常量区

ptr1 :是一个指向堆开辟空间的指针,处于栈

*ptr :是为堆开辟的空间 ,处于堆

sizeof(num1): 单独当在sizeof内部,数组名代表整个数组,sizeof(num1)=40

- sizeof(char2):单独当在sizeof内部,数组名代表整个数组,abcd\0,sizeof(char2)=5

- sizeof(pChar3):pChar3是一个指针,所以sizeof(pChar3)=4/8

strlen(pChar3):pChar3代表首元素地址,strlen为从给予的地址开始 到’\0’结束,strlen(pChar3)=4

- sizeof(ptr1):ptr1是一个指针 ,sizeof(ptr1)=4/8

2.c++管理方式

1.c++的内置类型

1.申请一个空间并初始化

#include<iostream>
using namespace std;
int main()
{
//int*ptr1=new int;//申请1个int的空间
    int* ptr = new int(10);//申请10个int的空间并初始化为10
 delete ptr;//释放单个空间
    return 0;
}

2.申请连续的空间并初始化

#include<iostream>
using namespace std;
int main()
{
    //int* ptr = new int(10);
    //int* ptr1 = new int[10];//申请10个int的空间
    int* ptr = new int[10] {1, 2, 3, 4};//申请10个int的空间并初始化
 delete []ptr;//释放连续空间
    return 0;
}

这里相当于部分初始化,只初始化了前4个空间,其他空间默认为0

3.总结

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]

2.c++的自定义类型

#include<iostream>
using namespace std;
class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = (A*)malloc(sizeof(A) * 10);
    free(p);
    A* p1 = new A[2];
    delete[]p1;
    return 0;
}
  • 申请2个A类型的空间,调用2次构造函数释放空间,并调用2次析构函数
  • 虽然写入了malloc在堆开辟10个A类型空间,free释放空间,但是没有调用构造和析构函数2.总结

在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

3.operator new与operator delete函数

operator new与operator delete函数是库里面提供的两个全局函数,不是运算符重载

new和delete是用户进行动态内存申请和释放的操作符,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

#include <iostream>
using namespace std;
#include<iostream>
using namespace std;
class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
int main()
{
    //申请空间 operator new  封装 malloc
    //在用operator delete p1指向空间
    A* p1 = new A;
    delete p1;
    //申请空间 operator new  封装 malloc
      //在用operator delete []p2指向空间
    A* p2 = new A[10];
    delete []p2;
    return 0;
}

4.new和delete的实现原理

1.内置类型

  • 对于是内置类型,malloc/free与new/delete功能基本一致,但new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间
#include<iostream>
using namespace std;
int main()
{
    int* p1 = (int*)operator new(sizeof(int));//new失败抛异常
    int* p2 = (int*)malloc(sizeof(int));
    if (p2 == nullptr)//malloc失败返回空
    {
        perror("malloc fail");
    }
    return 0;
}
  • new机制与malloc也不同,new申请空间失败会抛异常,而malloc失败返回nullptr

2.自定义类型

#include<iostream>
using namespace std;
class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
int main()
{
    A* p1 = new A;//先创建空间,在调用构造函数,
    delete p1;//delete先调用析构函数,在释放空间
    //-----------------------------------
    A* p2 = new A[10];//先创建空间,在调用构造函数10次
    delete[]p2;//delete先调用析构函数10次,在释放空间
    return 0;
}
  • new先申请一个A的空间,再调用构造函数,delete先调用析构函数,再释放空间
  • new先申请10个A类型的空间,再调用构造函数10次,delete先调用析构函数10次,再释放空间

内存泄露问题&&delete先析构的原因

class stack
{
public:
    stack()//构造
    {
        cout << "stack()" << endl;
        _a = new int[4];
        _top = 0;
        _capacity = 4;
    }
    ~stack()//析构
    {
        cout << "~stack()" << endl;
        delete[] _a;    
        _top = _capacity = 0;
    }
private:
    int* _a;
    int _top;
    int _capacity;
};
int main()
{
    stack p;
    stack*p1 = new stack;
    delete p1;
    return 0;
}

类的实例化对象生成p,在栈上,调用构造函数,在堆上开辟了4个stack类型的数组

p1是一个指针,在栈上,指向在堆上申请的一个stack, 再调用构造函数,_a=new

stack[4],_a再次指向在堆上申请的4个stack类型的数组,

所以必须先调用析构函数,在释放空间

若将delete p1改为 free(p1),会少调用析构函数,直接释放stack空间

导致无法释放堆上申请的4个stack类型的数组,从而导致内存泄露

编译器实现机制问题

#include<iostream>
using namespace std;
class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = new A[10];
    //delete p;//错误
    //free(p);//错误
 delete []p;//正确
    return 0;
}

正常来说,A*p=new A[10],我们知道会调用10次构造函数,但是delete [] p是怎么知道要调用10次析构函数的呢?

自定义类型A的大小为4个字节,申请10个A类型的数组,会开辟40个字节的空间,但是编译器会多开辟4个字节,用于存储个数 10,个数10是给delete时候用的

free ( p ) / delete p 时,释放的位置不对,所以会报错

delete[],就从当前指针p指向位置的地址往前减去4个字节,取到这个值(例如10),通过这个值就知道调用多少次析构函数

最终指针指向释放位置,从释放位置开始释放空间

5.定位new

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = (A*)malloc(sizeof(A));//开好一块空间
    if (p == nullptr)
    {
        perror("malloc fail");
    }
    //定位new
    new(p)A(1);//将p对象中的_a初始化为1
    p->~A();
    free(p);
    return 0;
}

对一块已有的空间进行初始化

定位new的使用场景

操作系统的堆因为给所有的地方提供,所以会慢一些


以前使用malloc/new申请内存,都是去操作系统的堆上申请的,直接申请


为了提高效率,申请内存去内存池中寻找,而内存池中内存也是堆上的,

如果内存池上有就直接返回,如果没有就会去堆上找,比如需要4个字节,内存池会申请大块的内存,储备到内存池中,下一次来申请内存,就能在内存池中找到


当在内存池中要的内存,而内存池要的内存没有初始化,所以需要定位new


6.malloc/free与new/delete的区别

1.共同点

都是从堆上申请空间,并且需要用户手动释放


2.不同点

用法角度

1.malloc和free是函数,new和delete是操作符

2.malloc申请的空间不会初始化,new可以初始化

3.malloc申请空间时,需要手动计算空间大小并传递,new只需其后跟上空间的类型即可,如果是多个对象时,[]指定对象个数即可

4.malloc返回值为void*,在使用必须强转,new不需要,因为new后跟的是空间类型

底层原理角度

malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new失败会抛异常

申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造和析构函数,而new申请空间后会调用构造函数完成对象的初始化,delete在释放空间会调用析构函数完成空间中的资源的清理


相关文章
|
5月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
191 26
|
10月前
|
存储 程序员 编译器
玩转C++内存管理:从新手到高手的必备指南
C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
|
6月前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
109 1
|
12月前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
497 68
|
9月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
10月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
447 0
|
11月前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
491 0
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
216 3
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
621 4
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。