菜鸟笔记
提升您的技术认知

C++ 内存分布

当编写C++程序时,合理地使用内存分配是非常重要的。以下是一份C++内存分布框架梳理,帮助你更好地理解和管理内存:

先给上一个内存分布图,从上到下,内存地址依次降低。

-------------------------------------------------------
|                      High Address                   |
--------------------------------------------------------
|                      Heap                            |
|                    (动态分配)                         |
|--------------------------------------------------- --|
|                      堆栈                            |
|                    (局部变量、函数参数等)               |
|-----------------------------------------------------|
|                     全局/静态存储区                    |
|                     (全局变量、静态变量)                |
|------------------------------------------------------|
|                     常量存储区                         |
|                    (常量值)                           |
|------------------------------------------------------|
|                     代码区                            |
|                     (执行代码)                        |
-------------------------------------------------------
|                      Low Address                     |
--------------------------------------------------------

栈(Stack):

栈上的内存分配是自动的,由编译器负责管理。
用于存储函数的局部变量函数参数函数调用的返回地址等。
栈的大小通常较小,适合存储相对较小的数据。
避免在栈上分配过大的数据,以免造成栈溢出。

#include <iostream>

// 函数声明
void display(int num);

int main() {
    int x = 5;  // 在栈上声明一个整型变量x,并初始化为5
    int y = 10; // 在栈上声明一个整型变量y,并初始化为10

    display(x); // 调用函数display,将x作为参数传递

    return 0;
}

// 函数定义
void display(int num) {
    // 在函数内部声明一个局部变量num,并将传入的参数值复制给它
    std::cout << "The number is: " << num << std::endl;
}

当程序执行到一个函数时,函数的局部变量和参数被分配在栈上;当函数执行完毕时,栈上的这些变量被销毁。

堆(Heap):

堆上的内存分配需要手动管理,使用new关键字进行分配,使用delete关键字进行释放。
用于存储动态分配的数据,如动态数组对象等。
避免内存泄漏,确保每次分配的内存都能被正确释放。

#include <iostream>

int main() {
    int* ptr = nullptr; // 声明一个指向整型的指针,并初始化为nullptr

    // 在堆上动态分配一个整型数组,包含5个元素
    ptr = new int[5];

    // 使用循环为数组赋值
    for (int i = 0; i < 5; ++i) {
        ptr[i] = i * 2;
    }

    // 打印数组的值
    std::cout << "Array elements: ";
    for (int i = 0; i < 5; ++i) {
        std::cout << ptr[i] << " ";
    }
    std::cout << std::endl;

    // 释放动态分配的内存
    delete[] ptr;
    ptr = nullptr;
    return 0;
}

全局/静态存储区(Global/Static Storage Area):

存储全局变量和静态变量。
全局变量在程序启动时被初始化,并在程序的整个生命周期中都存在。
静态变量也类似,但只在声明它的文件内可见。
避免滥用全局变量,尽量减少全局变量的数量,以免影响程序的可维护性和扩展性。

#include <iostream>

// 全局变量
int globalVar = 10;

// 函数声明
void func();

int main() {
    // 输出全局变量的值
    std::cout << "Global variable: " << globalVar << std::endl;

    // 调用函数
    func();

    // 再次输出全局变量的值
    std::cout << "Global variable after function call: " << globalVar << std::endl;

    return 0;
}

// 函数定义
void func() {
    // 修改全局变量的值
    globalVar = 20;
    std::cout << "Global variable inside function: " << globalVar << std::endl;

    // 静态变量
    static int staticVar = 5;
    staticVar++;
    std::cout << "Static variable inside function: " << staticVar << std::endl;
}

代码运行结果:
Global variable: 10
Global variable inside function: 20
Static variable inside function: 6
Global variable after function call: 20

globalVar 是一个全局变量,它在程序的整个生命周期内存在,并且可以在程序的任何地方访问。
func() 函数中修改了全局变量 globalVar 的值,并且声明了一个静态局部变量 staticVar,它的生命周期与程序的生命周期相同,但作用域仅限于 func() 函数内部。
在 main() 函数中调用了 func() 函数,并输出了修改后的全局变量的值。

常量存储区(Constant Storage Area):

存储程序中的常量值,如字符串常量、全局常量等。
这部分内存在程序启动时就已经分配,并在整个程序的生命周期内都不会被修改。
避免在常量存储区中修改常量值,以免引发未定义的行为。

#include <iostream>

int main() {
    // 字符串常量存储在常量存储区
    const char* str = "Hello, world!";

    // 输出字符串常量
    std::cout << "String constant: " << str << std::endl;

    return 0;
}

这块我经常搞混乱,有时候会把 str 变量的内存分配搞错,主要是带有*, 老是会理解成堆上的内存了,老想着去这个内存需不需要去释放。这个是栈内存,自己释放了哈。大家应该不会搞错,其实堆上内存都是动态的,一般都是new和malloc申请出来的。

在这个示例中:

“Hello, world!” 是一个字符串常量,它存储在常量存储区。
str 是一个指向字符常量的指针,它指向字符串常量 “Hello, world!” 的首地址。
当程序运行时,字符串常量 “Hello, world!” 被存储在常量存储区,而指针 str存储在栈上。输出指针 str 的值将显示字符串常量的内容。

代码区(Code Area):

存储程序的执行代码,包括函数体、指令等。
这部分内存在程序加载时分配,并在程序执行期间不会被修改。
避免在代码区中进行写操作,以免导致程序崩溃或其他严重问题。

#include <iostream>

// 函数声明
void sayHello();

int main() {
    // 调用函数
    sayHello();

    return 0;
}

// 函数定义
void sayHello() {
    std::cout << "Hello, world!" << std::endl;
}

在这个示例中:

sayHello() 函数的定义包含了要执行的指令,这些指令将在程序执行时加载到内存的代码区中。
在 main() 函数中调用了 sayHello() 函数,这会导致程序跳转到代码区中 sayHello() 函数的地址,并执行其中的指令,最终输出 “Hello, world!”。
在编译后的可执行文件中,函数的机器码指令将存储在代码区,等待被执行。

总的来说,希望大家看了后,能对内存分布有个简单的了解,合理地使用内存分配结构可以提高程序的性能和稳定性,同时避免常见的内存相关问题。
在编写C++程序时,应该深入理解每种内存分配结构的特点和使用方式,以及注意内存管理的细节,从而编写出高效、稳定的程序。