C++ 学习(九)内存分区(代码区、全局区、栈区、堆区)
操作系统将一整块内存划分了几个区域,每个区域用来做不同的事情存储程序的二进制指令,即程序源码编译后的二进制代码存储已被初始化的全局变量、常量存储未被初始化的全局变量,和data段一样都属于静态分配,在编译阶段就确定了大小,不释放(栈空间)主要用于函数调用时存储临时变量的,这部分的内存是自动分配,自动释放的(堆空间)主要用于动态分配,C语言中malloc和free操作堆内存,Go语言主要靠GC自动管
程序内存模型
1、操作系统内存区域
操作系统将一整块内存划分了几个区域,每个区域用来做不同的事情(如下图所示):
text段:存储程序的二进制指令,即程序源码编译后的二进制代码
data段:存储已被初始化的全局变量、常量
bss段:存储未被初始化的全局变量,和data段一样都属于静态分配,在编译阶段就确定了大小,不释放
stack段(栈空间):主要用于函数调用时存储临时变量的,这部分的内存是自动分配,自动释放的
heap段(堆空间):主要用于动态分配,C语言中malloc和free操作堆内存,Go语言主要靠GC自动管理这部分。
操作系统进程的内存区域没有这么简单,比上面要复杂的多,比如内核区域、共享库区域。我们主要关注堆空间与栈空间。
2、C++内存分区模型
C++程序执行时,将内存大致划分为四个区域
代码区:存放函数的二进制代码,由操作系统 进行管理
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
堆区:由程序员分配和释放,若不释放,程序结束时由操作系统回收
(1)程序运行前
在程序编译后,生成exe可执行程序(二进制文件),未执行该程序前分为两个区域:
代码区:
存放CPU执行的机器指令(二进制)
代码区是共享的,目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,为防止程序意外被修改了指令
全局区:
存储全局变量、静态变量、常量、字符串常量
全局区的数据在程序结束后由操作系统释放
全局区: 包含全局变量、静态变量、字符串常量、const修饰的全局变量
#include<iostream>
#include<string>
using namespace std;
//全局变量
int global_a = 6;
int global_b = 8;
//const修饰的全局变量
const int const_global_a = 7;
const int const_global_b = 9;
int main()
{
//程序内存模型 - 全局区:全局变量、静态变量、常量
//局部变量
int local_a = 5;
int local_b = 3;
cout << "局部变量local_a的地址:" << (int)&local_a << endl;
cout << "局部变量local_b的地址:" << (int)&local_b << endl;
cout << endl;
//全局变量输出
cout << "全局变量global_a的地址:" << (int)&global_a << endl;
cout << "全局变量global_b的地址:" << (int)&global_b << endl;
//静态变量
static int static_a = 10;
static int static_b = 12;
cout << "静态变量static_a的地址:" << (int)&static_a << endl;
cout << "静态变量static_b的地址:" << (int)&static_b << endl;
//常量
字符串常量
cout << "字符串常量的地址:" << (int)&"Hello" << endl;
//const修饰的变量
const 修饰的全局变量
cout << "const修饰的全局变量const_global_a的地址:" << (int)&const_global_a << endl;
cout << "const修饰的全局变量const_global_b的地址:" << (int)&const_global_b << endl;
cout << endl;
const 修饰的局部变量
const int const_local_a = 15;
const int const_local_b = 16;
cout << "const修饰的局部变量const_local_a的地址:" << (int)&const_local_a << endl;
cout << "const修饰的局部变量const_local_b的地址:" << (int)&const_local_b << endl;
system("pause");
return 0;
}
输出结果(全局区为浅蓝色字体并高亮标识)
局部变量local_a的地址:100006708
局部变量local_b的地址:100006740全局变量global_a的地址:-2080120832
全局变量global_b的地址:-2080120828
静态变量static_a的地址:-2080120824
静态变量static_b的地址:-2080120820
字符串常量的地址:-2080133432
const修饰的全局变量const_global_a的地址:-2080134044
const修饰的全局变量const_global_b的地址:-2080134040const修饰的局部变量const_local_a的地址:100006772
const修饰的局部变量const_local_b的地址:100006804
(2)程序运行后
栈区
由编译器自动分配释放,存放函数的参数值,局部变量等
注:不要返回局部变量的地址,栈区开辟的数据空间由编译器自动释放
#include<iostream>
using namespace std;
int* f1(int b)
{
int a = 5 + b;
cout << "函数内局部变量a地址:" << (int)&a << endl;
return &a; //返回局部变量地址
}
int main()
{
//程序内存模型 - 栈区:局部变量、形参存放在栈区,函数执行完后自动释放
int* p = f1(3);
cout << *p << endl; //第一次打印正确数字,是因为编译器做了保留????
cout << *p << endl;
cout << "主函数中调用函数f1后变量p的地址:" << (int)p << endl;
cout << *p << endl;
cout << *p << endl;
cout << "主函数中调用函数f1后变量p的地址:" << (int)p << endl;
system("pause");
return 0;
}
输出结果
函数内局部变量a地址:2019423876
8
8
主函数中调用函数f1后变量p的地址:2019423876
-858993460
-858993460
主函数中调用函数f1后变量p的地址:2019423876
堆区
由程序员分配释放,若不释放,程序结束时由操作系统回收
在C++中主要使用new关键字在堆区创建内存空间
#include<iostream>
using namespace std;
int* f1()
{
int* a = new int(5);
cout << "函数内局部变量a地址:" << (int)a << endl;
return a;
}
int main()
{
//程序内存模型 - 堆区:主要使用new关键字在堆区创建内存空间
int* p = f1();
cout << *p << endl;
cout << *p << endl;
cout << "主函数中调用函数f1后变量p的地址:" << (int)p << endl;
cout << *p << endl;
cout << *p << endl;
cout << "主函数中调用函数f1后变量p的地址:" << (int)p << endl;
system("pause");
return 0;
}
输出结果
函数内局部变量a地址:100964608
5
5
主函数中调用函数f1后变量p的地址:100964608
5
5
主函数中调用函数f1后变量p的地址:100964608
3、Go内存管理模型
Go内存是自动管理的,不需要像C++手动的 malloc 和 free 内存,malloc 的操作由Go编译器的逃逸分析机制加上了,free 的操作则由 GC 机制完成。
Go语言程序在启动时,会一次性向操作系统申请 一大块内存空间作为内存池,这块内存池由mheap管理(mheap是一个struct类型)。
关于Go内存管理模型详情参见:Go 的内存管理看这一篇就够了_cugbtang的博客-CSDN博客_go 内存管理
对应上面C++中的全局区、栈区、堆区的例子:
(1)全局变量
package main
import "fmt"
var global_a = 7
var global_b = 9
func main() {
//程序内存模型 - 全局区:全局变量、静态变量、常量
//局部变量
var local_a int = 5
var local_b int = 3
fmt.Printf("局部变量local_a的地址:%p\n", &local_a)
fmt.Printf("局部变量local_b的地址:%p\n", &local_b)
//全局变量输出
fmt.Printf("全局变量global_a的地址:%p\n", &global_a)
fmt.Printf("全局变量global_b的地址:%p\n", &global_b)
//Go语言,没有静态变量
fmt.Println("Go语言,没有静态变量")
//Go语言,常量地址是不允许访问的,下面写法语义会报错
fmt.Println("Go语言,常量地址是不允许访问的,下面写法语义会报错")
}
输出结果
局部变量local_a的地址:0xc0000a2058
局部变量local_b的地址:0xc0000a2070
全局变量global_a的地址:0xd56250
全局变量global_b的地址:0xd56258
Go语言,没有静态变量
Go语言,常量地址是不允许访问的,下面写法语义会报错
(2)栈区
Go语言中,如果函数返回局部变量地址,则会局部变量逃逸到堆中,不会出现C++函数调用完局部变量内存被回收不能多次访问的问题。
package main
import "fmt"
func main() {
p := f1(3)
fmt.Println("p = ", *p)
fmt.Println("p = ", *p)
fmt.Printf("主函数中调用函数f1后变量p的地址:%p\n", p)
fmt.Println("p = ", *p)
fmt.Println("p = ", *p)
fmt.Printf("主函数中调用函数f1后变量p的地址:%p\n", p)
}
func f1(b int) *int {
a := 5 + b
fmt.Printf("函数内局部变量a地址:%p\n", &a)
return &a
}
输出结果
函数内局部变量a地址:0xc00000a0a0
p = 8
p = 8
主函数中调用函数f1后变量p的地址:0xc00000a0a0
p = 8
p = 8
主函数中调用函数f1后变量p的地址:0xc00000a0a0
(3)堆区
package main
import "fmt"
func main() {
p := f1()
fmt.Println("p = ", *p)
fmt.Println("p = ", *p)
fmt.Printf("主函数中调用函数f1后变量p的地址:%p\n", p)
fmt.Println("p = ", *p)
fmt.Println("p = ", *p)
fmt.Printf("主函数中调用函数f1后变量p的地址:%p\n", p)
}
func f1() *int {
a := new(int)
*a = 5
fmt.Printf("函数内局部变量a地址:%p\n", a)
return a
}
输出结果
函数内局部变量a地址:0xc00000a0a0
p = 5
p = 5
主函数中调用函数f1后变量p的地址:0xc00000a0a0
p = 5
p = 5
主函数中调用函数f1后变量p的地址:0xc00000a0a0
程序中堆内存开辟与释放
C++堆内存开辟(new)与释放(delete)
使用new操作符在堆区开辟内存存储数据,由程序员手动开辟(new),手动释放(delete)
语法: new 数据类型; //返回数据类型的指针
#include<iostream>
using namespace std;
int* f1()
{
int* a = new int(5);
return a;
}
int main()
{
//new操作符:开辟内存
int* p = f1();
cout << *p << endl;
//delete释放内存
delete p;
//cout << *p << endl; //释放后再访问报错:读取访问权限冲突
cout << "-----开辟数组内存与释放内存-----" << endl;
int* arr = new int[5];
cout << arr[0] << endl;
//释放数组内存
delete[] arr;
//cout << arr[0] << endl;//释放后再访问报错:读取访问权限冲突
system("pause");
return 0;
}
输出结果
5
-----开辟数组内存与释放内存-----
-842150451
Go 堆内存开辟(new)与释放(自动)
Go语言中,使用new关键字在堆内存开辟空间,释放是在GC机制中自动释放,不需要像C++手动释放。
package main
import "fmt"
func main() {
var p *int = f1()
fmt.Println(*p)
fmt.Println("-----开辟数组内存与释放内存-----")
arr := new([5]int)
fmt.Println(arr[0])
fmt.Println("Go语言中,释放堆内存是在GC机制中自动释放,不需要像C++手动释放。")
}
func f1() *int {
a := new(int)
*a = 5
return a
}
输出结果
5
-----开辟数组内存与释放内存-----
0
Go语言中,释放堆内存是在GC机制中自动释放,不需要像C++手动释放。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)