程序内存模型

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的地址:-2080134040

const修饰的局部变量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++手动释放。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐