C++核心编程:内存分区模型

2023-10-27

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

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的。
  • 全局区:存放全局变量静态变量以及常量
  • 栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等。
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

内存四区的意义:

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





程序运行前:(全局区)

在程序编译后,生成了exe可执行程序未执行该程序前分为两个区域:

代码区:

  • 存放CPU执行的机器指令:二进制01010。
  • 特点:
    • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
    • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。

全局区:

  • 全局变量静态变量存放在此。
  • 全局区还包含了常量区字符串常量其他常量也存放在此。
    • 特点:
      • 该区域的数据在程序结束后操作系统释放

 其他常量:const修饰的全局变量。(全局常量

全局区存放的数据:全局变量、静态变量、常量。

局部变量:在函数体内声明的变量。包括main函数内。

全局变量:在函数体外声明的变量。

const修饰与否:

  • 常量:其值不可修改。
  • 变量:其值可修改。

static修饰与否:

  • 静态:
  • 非静态:
#include<iostream>
using namespace std;


// 创建全局变量
int g_a = 10;
int g_b = 20;

// const修饰的全局常量。
const int c_g_a = 10;
const int c_g_b = 20;


int main()
{
	// 全局区:存放:全局变量、静态变量、常量。

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

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

	cout << "全局变量g_a的地址位:" << (int)&g_a << endl;
	cout << "全局变量g_b的地址位:" << (int)&g_b << endl;

	// 静态变量: 在普通变量前面加上static
	static int s_a = 10;
	static int s_b = 20;
	cout << "静态变量s_a的地址位:" << (int)&s_a << endl;
	cout << "静态变量s_b的地址位:" << (int)&s_b << endl;
	cout << endl;

	// 常量:分为字符串常量和const修饰的变量。
	// 
	// 字符串常量:双引号引起来的字符串,都可以被称为字符串常量。"Hello world."。
	cout << "字符串常量的地址为:" << (int)&"hello world" << endl;

	// const修饰变量:还可以分为:const修饰的全局常量,const修饰的局部变量(局部常量)。
	cout << "全局常量 c_g_a的地址为:" << (int)&c_g_a << endl;
	cout << "全局常量 c_g_b的地址为:" << (int)&c_g_b << endl;

	cout << endl;   // 局部常量不在全局区。
	const int c_l_a = 10;
	const int c_l_b = 20;
	cout << "局部常量 c_l_a的地址为:" << (int)&c_l_a << endl;
	cout << "局部常量 c_l_b的地址为:" << (int)&c_l_b << endl;

	system("pause");
	return 0;
}

输出:

局部变量a的地址位:9435924
局部变量b的地址位:9435912

全局变量g_a的地址位:11583540
全局变量g_b的地址位:11583544
静态变量s_a的地址位:11583548
静态变量s_b的地址位:11583552

字符串常量的地址为:11574108
全局常量 c_g_a的地址为:11574920
全局常量 c_g_b的地址为:11574924

局部常量 c_l_a的地址为:9435900
局部常量 c_l_b的地址为:9435888

观察发现:

  • 全局变量、静态变量、全局常量、字符串常量的地址很近,他们放在同一块区域(全局区)中。 
  • 局部变量和局部常量的地址很近,他们放在同一块区域(栈)中。



程序运行后:(栈区)

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

注意事项:不要返回局部变量的地址,栈区开

辟的数据由编译器自动释放。

#include<iostream>
using namespace std;

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

int main()
{
	int* p = func();
	
	cout << *p << endl;   // 10
	cout << *p << endl;   // 2055768456


	system("pause");
	return 0;
}

输出: 

 第一次可以打印正确的数字。是因为编译器怕误操作,所以会做了一次保留。但是第二次这个数字就不再保留了。

所以局部变量的地址千万不要返回。因为运行完一次之后就被释放了。再用指针操作这个内存,这个内存就不再属于这个变量了。




新的问题:

假设当函数带有形参时:结果却又不同了:

#include<iostream>
using namespace std;

//int* func() 
int* func(int b) 
{
	b = 100;
	cout << "形参b的内存地址为:" << (int)&b << endl;    // 5242140
	int a = 10;  // 局部变量 存放在栈区,栈区的数据在函数执行完后自动释放。
	return &a;   // 返回局部变量的地址。
}

int main()
{
	int num = 1;
	int* p = func(num);
	cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572



	system("pause");
	return 0;
}

输出:

此时我将代码一步步屏蔽,试图查找原因:发现当

屏蔽掉main中的17行、18行打印时。

#include<iostream>
using namespace std;

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

int main()
{
	int num = 1;
	int* p = func();
	//cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	//cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572



	system("pause");
	return 0;
}

 

当我放开打印时:

#include<iostream>
using namespace std;

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

int main()
{
	int num = 1;
	int* p = func();
	cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	//cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572



	system("pause");
	return 0;
}

输出:

进一步实验:

#include<iostream>
using namespace std;

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

int main()
{
	int num = 1;
	int* p = func(num);
	//cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	//cout << "实参num的内存地址为:" << (int)&num << endl;  // 5242364
	
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572
	cout << *p << endl;   // 5312572



	system("pause");
	return 0;
}

 输出:

 也就是说只要main中不事先打印,不调用cout那就没事,一旦在打印指针之前,cout输出过一次,其他数据,那么这个指针就不变。

这个问题原因不知,如果有知道的小伙伴,请评论区留言告知我。



 堆区:new 和 delete

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

在C++中主要利用new关键字堆区开辟内存。

#include<iostream>
using namespace std;

int* func()
{
	//int a = 10;
	//return &a;
	// 
	// 利用 new关键字 将数据开辟到 堆区。
	// 指针本质也是局部变量,也是放在栈区,只是指针保存的实际数据在堆区。
	int* p = new int(10);
	return p;
}


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


	system("pause");
	return 0;
}

  

 指针是一种类型。4个字节,保存在栈中,栈的数据内容为指向的内存地址。

 用new关键字可以在内存堆中开辟空间存储数据。

A* p = new A(值);返回的是一个地址。

新建一个指向A类型的数据在堆中的地址这样一个变量。

可以由程序员释放内存空间:delete

#include<iostream>
using namespace std;


int* func()
{
	// 在堆区创建一个整型数据。
	// new 返回的是:该数据类型的指针。
	int* p = new int(10);
	return p;
}

void test01()
{
	int* p = func();
	cout << *p << endl;  // 10
	cout << *p << endl;  // 10
	// 在堆中的数据,只要程序员不主动释放,那么就一直存在。
	// 堆区的数据,由程序员管理开辟,程序员管理释放。
	// 如果想释放堆区的数据,利用关键字delete
	delete p;
	//cout << *p << endl;  // 引发了异常:读取访问权限冲突。内存已经被释放,再次访问就是非法,会报错。

}

void test02()
{
	// 在堆区开辟一个数组:
	int* arr = new int[10];   // 返回连续数组的首地址。
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	// 释放堆区数组:
	// 释放数组的时候,要加[]才可以。
	delete[] arr;

}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}



一行代码的解释:

看到一行代码:int a = 10;

int表示整型,在内存栈区中开辟4个字节用来保存数据。这个数据值为10。这个区域用a来表示。 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++核心编程:内存分区模型 的相关文章

随机推荐

  • Java8流式编程

    文章目录 流式编程 流 Stream Stream特点 Stream运行机制 迭代类型 外部迭代 内部迭代 二者区别 流的创建 数组创建 集合创建 值创建 函数创建 流的中间操作 distinct 去重 filter 过滤 sorted 排
  • LeetCode二叉树系列——236.二叉树的最近公共祖先

    一 题目描述 236 二叉树的最近公共祖先 给定一个二叉树 找到该树中两个指定节点的最近公共祖先 百度百科中最近公共祖先的定义为 对于有根树 T 的两个节点 p q 最近公共祖先表示为一个节点 x 满足 x 是 p q 的祖先且 x 的深度
  • 【数论基础】—— 隔板法

    隔板法 问题 n n n 个相同的小球 放到 m m m个不同的盒子里 盒子不能为空的方案数 n
  • Scala函数式编程之集合操作总结

    章节目标 掌握Iterable集合相关内容 掌握Seq集合相关内容 掌握Set集合相关内容 掌握Map集合相关内容 掌握统计字符个数案例 1 Iterable 1 1 概述 Iterable代表一个可以迭代的集合 它继承了Traversab
  • 举例详解数据分析会用到哪些SQL技能?

    本文首发个人知乎https zhuanlan zhihu com p 137328389和个人微信公众号 呆呆玩数据 背景介绍 在一家知名电商企业的BI部门实习四个多月 岗位为数据分析 日常工作中打交道最多的就是SQL和EXCEL 在实习之
  • Snipaste的使用

    Snipaste截屏软件的使用 1 开始截屏 第一种方式 快捷键 默认是F1 也就是说按一下F1键就会进入截屏状态 第二种方式 点击软件在任务栏上的图标 2 选定截屏区域 进入截屏状态后移动鼠标 软件会根据屏幕显示自动选择区域 你如果满意选
  • Linux常见错误 “cp: omitting directory”解决办法

    问题描述 在Linux系统使用cp 复制命令 复制目录时 常出现错误 cp omitting directory dir dir是需要复制的目录名称 是因为dir目录下存在其他目录或文件存在 不可只使用cp命令实现复制操作 解决方法 使用c
  • sqlite mysql_两款主流数据库对比,SQLite和MySQL哪款是你的菜?

    数据库是按照数据结构来组织 存储和管理数据的仓库 它产生于距今六十多年前 随着信息技术和市场的发展 特别是二十世纪九十年代以后 数据管理不再仅仅是存储和管理数据 而转变成用户所需要的各种数据管理的方式 数据库有很多种类型 从最简单的存储有各
  • 只用html如何实现音乐播放,如何使用html实现音乐播放

    如何使用html实现音乐播放 发布时间 2021 03 06 10 03 29 来源 亿速云 阅读 63 作者 小新 小编给大家分享一下如何使用html实现音乐播放 相信大部分人都还不怎么了解 因此分享这篇文章给大家参考一下 希望大家阅读完
  • 应届生自学《软件测试》全部内容合集-2个月稳拿offer

    在校生想成为软件测试工程师 需要自学些什么 我决定从以下三个方面分析 看完这篇文章你一定会有一个清晰明确的学习路线 如何自学软件测试 具体的学习方法是什么 应届生自学到什么程度可以找到测试工作 学习软件测试需要参加培训吗 PS 这里有一套2
  • Python自制恶搞virus

    这里我选择的是删除E盘所有文件和文件夹 需要注意的是 这些代码放进py文件后需要打包成exe文件才可以使用 如果想要删除其他磁盘文件 可以进行适当修改 若要删除C盘文件 可以将获取管理员权限的代码提前放置 import os import
  • 通讯中断 pc_通过CP343-1模块,如何实现2套S7-300之间的以太网通讯?

    我们首先搭建一套测试设备 设备的结构图如下 2套S7 300系统由PS307电源 CPU314C 2DP CPU314C 2PTP CP343 1 CP343 1 IT PC CP5611 STEP7组成 PLC系统概貌如下图 如下将向您一
  • Python tkinter将窗口显示在屏幕中心

    import tkinter as tk 创建主窗口 window tk Tk window title 第一个窗体 标题 获取屏幕尺寸计算参数 使窗口显示再屏幕中央 screen width window winfo screenwidt
  • LSTM的输入格式和输出个数说明

    前言 最近使用LSTM 神经网络进行预测的时候 突然发现对其输入输出不是很清晰 照着大佬们的改改还行 不能深入了解 有点难受呀 下面简单介绍一下 讨论 转换为LSTM的3D格式 samples time steps feature samp
  • SpringBoot整合activeMQ遇到的坑,以及整合案例

    一 出现以下异常时 需要注意 receive获取消息时不可有返回值 否则循环报此异常 Execution of JMS message listener failed and no ErrorHandler has been set 201
  • 泛型另解

    什么是泛型 我们在编写程序时 经常遇到两个模块的功能非常相似 只是一个是处理int数据 另一个是处理string数据 或者其他自定义的数据类型 但我们没有办法 只能分别写多个方法处理每个数据类型 因为方法的参数类型不同 有没有一种办法 在方
  • 解决python安装模块出现WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)

    你的系统时间不对 启动ntpd服务同步网络时间
  • 串联法和故事法

    课程目的 1 掌握串联法 掌握故事法 举例 老鼠一苹果一毛毛中一手表一鸭子 老鼠吃了一个苹果 苹果掉下来砸到了一条毛毛虫 毛毛虫带着手表 手表勒住了一只鸭子 1 串联法和故事法的区别 串联法 提到老鼠 只想到苹果 提到苹果 只想到毛毛虫 提
  • CTeX使用全过程

    最近投稿时编辑部给的模板居然是古老的CCT Mac OS的我直接懵逼了 这也让我重新翻出来那台8年前的Dell 期中也遇到了一系列的问题 现在解决了就Mark一下吧 养成好习惯 CTeX C T e X CTeX CTeX是什么 CTEX是
  • C++核心编程:内存分区模型

    C 程序在执行时 将内存大方向划分为4个区域 代码区 存放函数体的二进制代码 由操作系统进行管理的 全局区 存放全局变量和静态变量以及常量 栈区 由编译器自动分配和释放 存放函数的参数值 局部变量等 堆区 由程序员分配和释放 若程序员不释放