小古银的官方网站(完整教程):http://www.xiaoguyin.com/
C++入门教程视频:https://www.bilibili.com/video/av20868986/
目录
指针的用途二:保存申请堆内存后返回的地址。这个用途应该用智能指针代替。
堆内存
堆内存在内存上和栈内存的区别是:申请栈内存有上限而且很少,当达到上限时操作系统不再给程序分配内存,程序就会崩溃;而申请堆内存理论上是会得到内存条剩余可用内存,实际上大多数情况下,申请堆内存是没有上限的,操作系统都有办法分配给你(操作系统的虚拟内存技术)。
堆内存在使用上与栈内存不同的是:栈内存运行到离开作用域时操作系统会自动回收内存;而堆内存则需要程序员自己手动释放。如果不断申请堆内存而不释放的话,可用的内存将越来越少,直至操作系统卡死。
堆内存的申请和释放
基础示例
向操作系统申请堆内存使用关键字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
。
用堆内存数据来进行处理时,一般都会尽量在同一个作用域内创建和释放,即在同一个作用域内new
和delete
,这个是正常的用法。以下这种做法是非常容易出错的:在一个函数里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;
}
虽然我知道你看这个可能会有点蒙,但是没关系,我的重点不是这个。我的重点是:
使用智能指针就不需要注意这个。
补充知识(了解即可)
虚拟内存技术:简单地说,就是操作系统不够内存分配的时候,将不太忙的程序(包括操作系统)的内存暂时用硬盘保存,保存完毕后将这个内存分配给需要的程序。由于操作系统也会将自身的内存给程序,而且硬盘的读写慢,当操作系统自身不够内存时,就会直接卡死。