C++编程——内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

程序运行前:

  • 代码区:存放函数体的二进制代码,由操作系统进行管理

  • 全局区:存放全局变量和静态变量以及常量

程序运行后:

  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等

  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:

不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

1.1 程序运行前

在程序编译后,生成exe可执行程序。未执行程序前,只有两个区在工作。

代码区:

存放CPU执行的机器指令(0/1机器码)

代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

代码区是只读的,目的是防止程序以外的修改了代码区的指令

全局区:

全局变量和静态变量存放在此

全局变量还包含了常量区,字符串常量和其他常量( const 修饰的变量)也存放在此

该区域的数据在程序结束后由操作系统释放

#include<iostream>
using namespace std;

int main()
{
    //全局区

    //全局变量/静态变量/常量

    //创建普通局部变量
    int a = 10;
    int b = 10;

    cout << "局部变量a的地址为:" << (int)&a << endl;
    cout << "局部变量b的地址为:" << (int)&b << endl;


    return 0;
}

编译报错:
------------------------------------------------------------------------------
全局区.cpp: In function ‘int main()’:
全局区.cpp:14:37: error: cast from ‘int*’ to ‘int’ loses precision [-fpermissive]
   14 |     cout << "局部变量a的地址为:" << (int)&a << endl;
      |                                     ^~~~~~~
全局区.cpp:15:37: error: cast from ‘int*’ to ‘int’ loses precision [-fpermissive]
   15 |     cout << "局部变量b的地址为:" << (int)&b << endl;
      |                                     ^~~~~~~

报错是因为尝试将一个整数指针(int*)转换成一个整数(int)。在32位系统上,这种转换通常可以正常工作,因为指针和整数通常都是32位的。但在64位系统上,指针是64位的,而int通常是32位的。因此,当你尝试将一个64位指针转换为32位整数时,编译器警告你这种转换会丢失精度。

解决这个问题的一种方法是将指针转换为uintptr_t类型,这是一个无符号整型,其大小足以存放指针,无论在32位还是64位系统上。请记得包含头文件 <cstdint> 来使用uintptr_t类型。这样的转换能够保持地址的完整性,避免精度丢失的问题。

改成下面的样式即可:

#include <iostream>
#include <cstdint> // 添加这行来使用 uintptr_t
using namespace std;

//全局变量,定义在函数外面
int g_a = 10;
int g_b = 10;

//const修饰的全局变量,即全局常量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {

    //普通局部变量,定义在main函数里面
    int a, b;
    cout << "局部变量a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&a)) << endl;
    cout << "局部变量b的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&b)) << endl;

    cout << "全局变量a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&g_a)) << endl;
    cout << "全局变量a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&g_b)) << endl;
    
    //静态变量,在普通局部变量前加static,在内存中的位置邻近全局变量
    static int s_a = 10;
    static int s_b = 10;

    cout << "静态变量a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&s_a)) << endl;
    cout << "静态变量a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&s_b)) << endl;

    //常量

    //字符串常量
    cout << "字符串常量的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>("Hello World")) << endl;

    //const修饰的常量

    //const修饰的全局常量
    cout << "const修饰的全局常量c_g_a的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&c_g_a)) << endl;
    cout << "const修饰的全局常量c_g_b的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&c_g_b)) << endl;
    //const修饰的局部常量
    const int c_l_a = 10; // c-const  g-gloabal  l-local
    const int c_l_b = 10;
    cout << "const修饰的局部常量c_l_b的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&c_l_b)) << endl;
    cout << "const修饰的局部常量c_l_b的地址为:" << static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(&c_l_b)) << endl;

    return 0;
}


输出:
-----------------------------------------------------------------------------------
局部变量a的地址为:140723000827512
局部变量b的地址为:140723000827516
全局变量a的地址为:110713891356688
全局变量a的地址为:110713891356692
静态变量a的地址为:110713891356696
静态变量a的地址为:110713891356700
字符串常量的地址为:110713891348633
const修饰的全局常量c_g_a的地址为:110713891348488
const修饰的全局常量c_g_b的地址为:110713891348492
const修饰的局部常量c_l_b的地址为:140723000827520
const修饰的局部常量c_l_b的地址为:140723000827520

总结:

  • C++中在程序运行前分为全局区和代码区

  • 代码区特点是共享和只读

  • 全局区中存放全局变量/静态变量/常量

  • 常量区(全局区的一部分)中存放 const 修饰的全局常量和字符串常量

1.2 程序运行后

栈区:

由编译器自动分配释放,存放函数的参数值(形参),局部变量等

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#include<iostream>
using namespace std;

//栈区数据注意事项
//1.不要返回局部变量的地址
//2.栈区的数据由编译器管理开辟和释放


int* func(int b)  //形参b也会放在栈区
{
    int a = 10;  //局部变量,存放在栈区,栈区的数据在函数执行完后自动释放
    return &a;  //返回局部变量的地址
}


int main()
{
    int * p = func(1);
    cout << *p << endl;

    return 0;
}


编译器报错:
------------------------------------------------------------------------------
栈区.cpp: In function ‘int* func()’:
栈区.cpp:12:12: warning: address of local variable ‘a’ returned [-Wreturn-local-addr]
   12 |     return &a;  //返回局部变量的地址
      |            ^~
栈区.cpp:11:9: note: declared here
   11 |     int a = 10;  //局部变量,存放在栈区,栈区的数据在函数执行完后自动释放
      |         ^

这个警告消息是编译器在告诉你有潜在的危险操作:你正在尝试从一个函数中返回一个局部变量的地址。局部变量是存储在函数调用栈上的,这意味着当函数执行完毕并返回后,局部变量的内存空间将被释放回操作系统,并可能被后续的函数调用或其他数据覆盖。因此,局部变量的地址在函数外部是无效的,任何尝试访问该地址的操作都是不确定的,通常会导致不稳定的行为或程序崩溃。

解决这个问题的方法通常有以下几种:

  1. 使用全局变量或静态变量:这些变量的生命周期覆盖了程序的整个运行时期,因此返回它们的地址是安全的。但请注意,过度使用全局变量会使程序的结构变得混乱,不易维护。

  2. 动态分配内存:使用动态内存分配(例如,C++中的new或C中的malloc)来创建对象或数据。动态分配的内存位于堆区,它的生命周期直到使用delete(C++)或free(C)显式释放之前都有效。这意味着你可以安全地返回指向这些内存的指针,但必须记得最终释放它以避免内存泄漏。

  3. 使用引用或指针传递:如果可能,通过引用或指针传递变量到函数外部,而不是返回指针或引用。这样,数据的管理(创建和销毁)可以在调用函数外部进行,从而避免了从函数内部返回局部数据的问题。

  4. 返回值:如果函数的目的只是要返回某种计算的结果,最简单的办法可能就是直接返回计算结果的值,而不是返回指向该结果的指针或引用。

下面是一个修正示例,如果你的目标是返回一个整数值:

int func() {
    int a = 10;  // 局部变量
    return a;   // 直接返回值
}

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用 new 在堆区开辟内存

#include<iostream>
using namespace std;


int* func()  
{
    //利用 new 关键字,可以将数据开辟到堆区,并返回数据对应的地址
    //指针本质也是局部变量,放在栈上,但指针保存的数据放在堆区
    int * p = new int(10);  
    return p;  
}


int main()
{
    //在堆区开辟数据
    int * p = func();
    cout << *p << endl;

    return 0;
}


输出:
-----------------------------------------------------------------------------------
10

1.3 new操作符

C++中利用new操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

语法:new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针(即数据对应的地址)

#include<iostream>
using namespace std;

//1.new的基本语法
int* func()  
{
    //在堆区创建整形数据
    //new返回的是对应数据类型的指针
    int * p = new int(10);  
    return p;  
}

void test01()
{
    //堆区的数据,由程序员管理开辟和释放
    int * p = func();
    cout << *p << endl;
    cout << *p << endl;
    cout << *p << endl;
    //释放堆区数据
    delete p;
    // cout << *p << endl;  //内存已经被释放,再次访问就是非法操作,会报错
}

//2.在堆区利用new开辟数组
void test02()
{
    //在堆区创建包含10个整形数据的数组
    int * arr = new int[10];

    for (int i = 0; i < 10; i++)
    {
        arr[i] = i + 100;  //给数组元素赋值,100~109
    }

    for (int i = 0; i < 10; i++)
    {
        cout << arr[i] << endl;
    }
    
    //释放堆区数组
    delete[] arr;
}

int main()
{
    test01();
    test02();

    return 0;
}


输出:
----------------------------------------------------------------------------------------------------
10
10
10
100
101
102
103
104
105
106
107
108
109

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值