• Index

堆内存

Reads: 10

指针的用途二:保存申请堆内存后返回的地址。这个用途应该用智能指针代替。

堆内存

堆内存在内存上和栈内存的区别是:申请栈内存有上限而且很少,当达到上限时操作系统不再给程序分配内存,程序就会崩溃;而申请堆内存理论上是会得到内存条剩余可用内存,实际上大多数情况下,申请堆内存是没有上限的,操作系统都有办法分配给你(操作系统的虚拟内存技术)。

堆内存在使用上与栈内存不同的是:栈内存运行到离开作用域时操作系统会自动回收内存;而堆内存则需要程序员自己手动释放。如果不断申请堆内存而不释放的话,可用的内存将越来越少,直至操作系统卡死。

堆内存的申请和释放

基础示例

向操作系统申请堆内存使用关键字new,将堆内存释放还给操作系统使用关键字delete

#include <iostream>

int main(void)
{
    int *pointer = new int(2333); // 申请int类型的堆内存, 并且将内存数据初始化为2333
    std::cout << *pointer << std::endl; // 输出内存中的数据
    delete pointer; // 释放堆内存
    pointer = nullptr; // 赋值为空, 防止误操作
    return 0;
}

输出结果:

2333

基础讲解

以下代码只是申请堆内存:

auto pointer = new int;

以下是申请堆内存然后初始化内存中的数据(前面教程说过int()就是int(0)):

auto pointer = new int();

当使用new向操作系统申请堆内存后,操作系统就会找有没有可用内存,如果有,操作系统就会将这份内存分配给程序,并把这份内存的首地址返回给程序以供对内存操作,然后你需要用指针保存下来;如果没有可用内存,则new会抛出异常(异常将在后续讲解),但是现代的操作系统有虚拟内存技术,所以不会没有可用内存。

当不需要内存的时候,需要用delete 堆内存的地址;手动释放堆内存。由于内存已经被释放,指针再保存这个地址将很有可能误操作,所以需要及时赋值为nullptr

新手常犯错误

错误一:

#include <iostream>

int main(void)
{
    {
        auto pointer = new int(2333);
    } // 错误, 栈内存自动释放, 堆内存没有被释放
    return 0;
}

前面教程说得很清楚,聪明的你肯定知道栈内存会自动释放, 堆内存要手动释放;应该也知道pointer是栈内存,只是保存了堆内存的地址,并不是堆内存。

上面代码中,pointer作为变量是栈内存,会自动释放;但是new出来的是堆内存,需要手动释放。而上面代码只有pointer知道堆内存的地址,当pointer自动释放后,堆内存地址就没有任何人也没有任何变量知道了。那么这份堆内存就找不到了,也就是说,这份堆内存永远都释放不了,这种情况叫做内存泄漏

错误二,这是更常见的粗心大意:

#include <iostream>

int * create_int(int value)
{
    return new int(value); // 堆内存不会自动释放, 这里没有错
}

int main(void)
{
    auto pointer = new int(666);
    pointer = create_int(2333); // 错误, 内存泄漏
    delete pointer; // 不良设计
    pointer = nullptr;
    return 0;
}

这里有两个问题,是新手常见的。虽然上面代码新手也可以清晰看出来会内存泄漏,但是新手代码一写长一点,就很容易忽略这个内存泄漏,这是我看到很多萌新容易出错的地方。下面详细讲解。

对于内存泄漏的错误,新手写代码写着写着就萌萌哒,就觉得pointer = create_int(2333);是给pointer“赋值”,这个真的是赋值,不过要看清楚,赋的是地址而不是2333哟~。给pointer再赋值,导致原来的堆内存没人知道它的地址,从而造成内存泄漏。经常加班的人也容易犯这个错误,因为加班使人萌萌哒。 o(〃'▽'〃)o

接下来就是说这个不良的设计,这种设计很容易使人忘记delete

用堆内存数据来进行处理时,一般都会尽量在同一个作用域内创建和释放,即在同一个作用域内newdelete,这个是正常的用法。以下这种做法是非常容易出错的:在一个函数里new之后处理一点,然后在其他函数又处理一点再delete,这个真的是作死。 Ծ‸ Ծ

如果功能是类似我上面代码这种:需要用一个函数创建一份内存,那么也请务必再写一个对应的释放函数。例如:create_int()release_int()一套、allocate()deallocate()一套。不要只给出单独一个,好寂寞的。

使用智能指针就没有以上烦恼。

注意异常

异常将在后续详细讲解,现在先大概了解。

以下代码看上去不会内存泄漏。而实际上当text是字符串"abc"(即不是数字)时,std::stoi()会抛出异常,并且会在这一行令函数直接退出,不继续执行剩下的代码,这样也会导致内存泄漏。

int to_int(const std::string &text)
{
    // 假设这个指针是非常必要的
    auto pointer = new int();
    *pointer = std::stoi(text); // 当text是"abc"时会直接退出函数导致内存泄漏
    auto num = *pointer;
    delete pointer;
    pointer = nullptr;
    return num;
}

虽然我知道你看这个可能会有点蒙,但是没关系,我的重点不是这个。我的重点是:

使用智能指针就不需要注意这个。

补充知识(了解即可)

虚拟内存技术:简单地说,就是操作系统不够内存分配的时候,将不太忙的程序(包括操作系统)的内存暂时用硬盘保存,保存完毕后将这个内存分配给需要的程序。由于操作系统也会将自身的内存给程序,而且硬盘的读写慢,当操作系统自身不够内存时,就会直接卡死。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.