内存管理

C++程序运行时内存大致可以被分为5个区域,代码区(Code Segment)、全局区(Data Segment)、常量区(Constant Segment)、栈区(Stack)、堆区(Heap),我们代码里对应的变量会被编译器设置为运行时分配在合适的内存区域中。这篇笔记我们介绍C++程序运行时的几个内存区域以及如何对内存进行管理。

内存区域

可执行程序的内存分区模型是由操作系统管理的,不同的操作系统、不同的可执行文件格式可能给出不同的内存模型实现,因此我们不能确定的说C++程序运行时的内存模型就一定是怎样的,不过大致上主流操作系统都遵循类似下面的模型实现。

代码区:代码区存放程序的机器指令(即编译后的代码),代码区通常是只读的,这是为了防止程序意外修改指令,此外代码区一般是共享的,多个进程可以共用同一份代码区,节省内存开销。

全局区:全局区存储所有全局变量和static变量,全局区主要分为两部分,已初始化区和未初始化区(BSS区),未初始化的全局变量或static变量会被设置为0。全局区在程序整个生命周期内都存在。

常量区:常量区存储常量,例如const定义的常量、字符串字面值等。常量区的只读的,尝试修改常量区内存会造成运行时错误。

栈区:栈区存放函数的参数、局部变量、返回地址、寄存器等调用现场信息。应用程序的栈内存基于栈这种数据结构的特性,它是自动分配和释放的,我们无需手动管理,栈虽然空间较小但访问速度较快,此外如果栈存入了过多的数据(通常是无限递归等错误造成的)就会抛出栈溢出运行时错误。

堆区:堆区可以说是最“通用”的一种内存空间,C++程序员可以手动分配堆内存,用完后则需要手动释放内存。堆内存空间很大,它适合存储大量或生命周期不确定的数据,但如果分配内存后忘记释放则可能出现内存泄漏问题,释放过早在内存释放后还读取内存的错误用法则被称为野指针问题。如何管理堆内存也是C++的难点和易出Bug的地方。

有关程序的内存分区模型,还可以参考/软件工程/操作系统/汇编语言基础相关章节,这些章节从汇编角度给出了内存区域分配相关的更底层解释。

堆内存管理

我们知道C语言中堆内存分配和释放使用的是malloc()free()函数,它们通常是成对出现的。C++中也是类似的,C++中我们使用newdelete关键字管理内存,它们也必定是成对出现的。

下面例子代码中,我们在堆内存上开辟空间,存入了一个整数1new关键字开辟内存后我们得到的是一个指针,我们使用解引用操作符访问其中数据,最后使用delete关键字释放内存,注意delete后面也需要使用指针。

#include <iostream>

int main() {
    int *p = new int(1);
    std::cout << *p << std::endl;
    delete p;
    return 0;
}

对于数组也是类似的,下面例子演示了如何在堆内存上分配数组。

#include <iostream>

int main() {
    int *arr = new int[3]{1, 2, 3};
    for (int i = 0; i < 3; i++) {
        std::cout << arr[i] << std::endl;
    }
    delete[] arr;
    return 0;
}

new也常用于在堆内存上创建类的实例,下面是一个例子。

#include <iostream>

class Cat {
    std::string name;

public:
    Cat(std::string name) : name(name) {
    }

    void meow() {
        std::cout << "Hi, my name is " << name << "! Meow~" << std::endl;
    }
};

int main() {
    Cat *pussy = new Cat("Pussy");
    pussy->meow();
    delete pussy;
    return 0;
}
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap