From Java to C++ 之内存管理篇

作者: i校长 | 来源:发表于2021-05-15 17:05 被阅读0次

前叙

From Java to C++ 第一篇
From Java to C++ 第二篇
From Java to C++ 第三篇
在前面三篇中,从快速入门,再细节到C++实参传递特点,这次我们从最最基础,恰巧也是最最重要的部分,内存管理,为什么说它重要呢?因为在C++中并没有提供像Java一样的完善的垃圾回收机制,就算有也是比较简单的,并不能作为完美的依靠,但恰巧是因为开发可以自己控制内存,来达到更加高效的内存管理,虽然现在这个年代好像说内存并不那么的重要,所以来让Java、Python这种语言火了起来,说白了它们就是用空间换时间,但是作为一个追求完美的内存管理者,我们不光追求更短的时间,也在追求更小的空间,这些就离不开C或者C++,我们都知道Java中的堆栈,其实Java作为C++的后继语言,它其实就是借鉴了C++的做法,但C++中有RALL,是C++所特有的资源管理方式,下面我们先来学习下这三个概念。

在内存管理下,它是函数调用过程中产生的变量,函数参数值、返回变量等的一块内存区域,和栈数据结构类似,遵循后进先出的特点。在Java中栈(虚拟机栈、本地方法栈)是线程私有的内存。其实栈的内存管理很好理解,就是出栈后,随之变量和对象都会被释放,那它是如何释放的呢,我们来看个例子


栈.png

简单类型的释放应该很简单,如果是对象的话,它有构造函数等,在释放的时候其实就会调用对应的析构函数,哪怕发生了异常退出,C++内存管理都会执行对象的析构函数来释放。所以你是不是了解了析构函数的作用了呢?

在C++中,和Java一样都是属于动态分配的区域,而且都是靠 new关键字来申请空间,但唯一不同的是,在C++中需要显示的 delete,才可以释放掉,而Java则是通过GC回收。所以C++中,如果你用new来创建对象,那就要和delete成队出现,但C++中还有个问题,一般你不会new完以后直接delete,实际的场景其实是你new完以后,需要很多操作,然后在delete,但这中间有可能发生崩溃,导致程序未能按照以前的想法执行delete操作,所以,这就产生了内存泄漏,内存永远无法释放掉,时间久了就会导致应用内存占满,无法申请新的空间,其实C++给我们提供了智能指针等可以优雅的释放该内存,后续我们专门找个课题研究这个如何更好的回收内存。 堆.png

如图,你也看到了当我们new的时候,其实内存管理,它经历了分配内存,其实这块内存在分配和释放时,还会考虑如下场景:

  1. 内存充足,从可用的内存里取出一块合适大小的内存
  2. 内存充足但可用内存中没有合适的大小,这里的情况其实内存管理还会做一个操作就是合并未使用的内存,为什么会是这样呢?请看图你就明白了

比如我要的内存是4,其实这个状态是够用的,但不连续,所以这种情况,就需要内存管理来做整理,其实还好,由于C++有专门的内存碎片管理机制,所以第二种情况也不用你管理什么,我们只关心正确的new和delete就行了。

  1. 内存不足时要从操作系统申请新的内存

RALL

英文是Resource Acquisition Is Initialization,直译是资源获取即初始化,完全不理解,这东西源于C++,其实Java中也有运用,具体怎么用我也不知道,感兴趣的可以研究下。
它的来源:
比雅尼·斯特劳斯特鲁普安德鲁·柯尼希在设计C++异常时,为解决资源管理时的异常安全性而使用了它。
RAII要求:
资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
说了这么多你肯定也跟我一样不懂,再现实一点,RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理,所以说它管理的东西可多了,栈、堆以及其他资源吧。
在C++中,栈上面是可以创建对象的,但是栈内存一般会很小,且它是一块连续的内存区域,不像堆一样可以使用不连续的内存区域,底层用链表构成,在Window下,栈的大小是2MB,Linux下,默认栈空间大小为8MB,当然也可以修改。所以,如果你将对象都创建的栈上,而不用堆的内存,那肯定是不够的。
所以不管是参数,函数内声明的变量,还是返回值,如果是对象的话,我们大部分是依赖的引用或者指针,而引用的值和指针的值其实是放在堆里的。Java也是一样。
为了能更好的理解RALL,我们先来看个例子


class TestRALL {
public:
    TestRALL() {
        std::cout << "TestRALL done" << std::endl;
    };

    ~TestRALL() {
        std::cout << "~TestRALL done" << std::endl;
    };

    void print() {
        std::cout << 1 << std::endl;
    }
};

TestRALL *createTest() {
    return new TestRALL();
}

void print() {
    auto ta = createTest();
    ta->print();
}

int main() {
    print();
    return 0;
}

执行main后输出如下:

TestRALL done
1

发现,并没有调用析构函数,意味着TestRALL对象一直在。如果我加入这么一行

void print() {
    auto ta = createTest();
    ta->print();
    delete ta;
}

打印

TestRALL done
1
~TestRALL done

其实我这里就是显式的调用了delete,其实平时我们这样用不科学,那我该如何做呢?再来看下面的例子


class TestRALL {
public:
    TestRALL() {
        std::cout << "TestRALL done" << std::endl;
    };

    ~TestRALL() {
        std::cout << "~TestRALL done" << std::endl;
    };

    void print() {
        std::cout << 1 << std::endl;
    }
};

TestRALL *createTest() {
    return new TestRALL();
}


class TRDelete {
public:
    explicit TRDelete(TestRALL *tr = nullptr) : tr_(tr) {}

    ~TRDelete() {
        delete tr_;
    }

    TestRALL *get() const { return tr_; }

private:
    TestRALL *tr_;
};

void print() {
    TRDelete trDelete(createTest());
    trDelete.get()->print();
}

int main() {
    print();
    return 0;
}

首先解释个新东西:

explicit

构造函数被explicit修饰后, 就不能再被隐式调用,什么是隐式调用?请看个例子:

#include <iostream>
using namespace std;

class Point {
public:
    int x, y;
    Point(int x = 0, int y = 0)
        : x(x), y(y) {}
};

void displayPoint(const Point& p) 
{
    cout << "(" << p.x << "," 
         << p.y << ")" << endl;
}

int main()
{
    displayPoint(1);
    Point p = 1;
}

displayPoint就是隐式调用,看着是简化了代码的写法,但为什么会不推荐呢?来自Effective C++,因为如下:
被声明为explicit的构造函数通常比其 non-explicit 兄弟更受欢迎, 因为它们禁止编译器执行非预期 (往往也不被期望) 的类型转换. 除非我有一个好理由允许构造函数被用于隐式类型转换, 否则我会把它声明为explicit. 我鼓励你遵循相同的政策。
回过头来看上面的TRDelete,在它的析构函数中,我们delete TestRALL,在print函数执行完后,我们并没有执行delete trDelete 那它为什么会执行TRDelete的析构函数呢?哈哈其实很简单,因为TRDelete并不是通过new创建的,无需delete,它在函数出栈的时候,自然会调用到TRDelete自己的析构函数,这就是RALL的一个基本用法。其实还有更加智能的用法,以后我们再学习讨论。

简单总结

这次我们对栈、堆、RALL的内存管理特点做了学习和练习,也知道了可以通过RALL对栈和堆的内存统一管理的一个小用法,当然我们学习要循循渐进,一步一个juo印,欢迎你留言讨论哈。

相关文章

  • 内存管理

    内容包括: C++内存管理 Java内存管理 C++内存管理 内存分配方式 在C++中,内存分成5个区,分别是栈、...

  • From Java to C++ 之内存管理篇

    前叙 From Java to C++ 第一篇[https://juejin.cn/post/6958827319...

  • Java内存泄漏

    本文将会介绍: C++中的内存泄露 Java内存管理与垃圾回收 Java中的内存泄漏 一、C++中的内存泄露 在大...

  • 编程语言介绍

    Java:跨平台,自动内存管理; python: ; c:; c++:Essential C++,C++Prime...

  • Java 内存管理

    Java可以自动管理内存,比C/C++要方便的多, 但是实际Java 也会出现内存溢出的问题。 关于Java的内存...

  • Java四种引用类型

    java不同于c/c++,它不需要程序员自已来管理内存(分配,释放内存),java 会自己来管理内存,比如销毁某些...

  • Q:Java有几种引用类型?

    Java有几种引用类型 引自 java 知识 之 内存管理 Java 中的内存管理包括内存分配和内存回收,这些都是...

  • c++内存管理

    c++内存管理长文 c++内存管理

  • Java 内存空间

    1. 概述 Java 不像 C/C++ 需要程序员自己管理内存,Java 把内存控制的权利交给类 Java 虚拟机...

  • java 多种引用的使用说明

    前言 总所周知, java不同于c/c++,它不需要程序员自已来管理内存(分配,释放内存),java 会自己来管理...

网友评论

    本文标题:From Java to C++ 之内存管理篇

    本文链接:https://www.haomeiwen.com/subject/mdckjltx.html