C/C++内存四区模型

1 篇文章 0 订阅

关于内存

内存(Memory)是计算机极其重要的一个部分,也是手机电脑玩家攀比的一个重要指标。但内存一直是很多学习C和C++的学生一大难点。而很多同学至今仍然分不清磁盘RAM内存有什么不一样。。

A:你新买的电脑内存多大呀?

B:我的是16G内存。

A:我的手机都有64G内存欸。。

不论你是否充当过上述对话里面的角色A,首先明确一点:以下讨论的内存,均不是指磁盘内存。有关更多磁盘内存和ram的区别,可以去翻阅其他更加专业的资料。这里说的内存,都是你可以在任务管理器里面看到的那个内存。

内存

这才是我们讨论的内存

内存四区

以笔者目前看过的教程来讲,关于内存模型,各个教程差别很大,都采用不同的方式,甚至还和生存期混起来讲解。给我也是造成了很大的疑惑。我这里主要介绍模型是我认为的比较全面的内存模型,该模型把程序内存分为四个区,如下:

内存四区存放的东西
代码区(code)你写的代码,各种函数(包括类成员函数)
全局区(global)全局&静态&常量:全局(global)变量,静态(static)变量,各种常量(字符串字面量,const修饰的量)
栈(stack)局部变量&函数参数:在函数内声明的变量(包括main函数)
堆(heap)使用new,malloc,colloc等关键字手动开辟的内存

代码区

比起其他几个区,代码区不为人熟知。

代码区存放着函数,我们平时并不会去关注一个函数的地址,所以很多人并不知道代码区的存在。

代码区存放一般函数

有人可能好奇,运行下面这样的代码:

int func(){return 0;}
...
cout<< func <<endl;

或者是:

cout<< &func <<endl;

如果是gcc,你可能会得到如下警告:

warning: the address of 'int func()' will never be NULL [-Waddress]

忽视警告,打印出结果,发现都是1。这就是因为函数存放于代码区的缘故。

代码区存放类函数

类函数是对象的方法,会给人一种对象内含了函数的错觉。假设有下面一个类:

class Cat
{
    private:
        int age;
        double length;
        bool gender;
    public:
        void eat();
        void sleep();
};
Cat Tom;

我们创建一个Cat对象Tom,获得Tom的占用内存大小:

cout<< sizeof(Tom)<<endl;

结果是24。这里是因为内存对齐的原因,把boolint的内存也对齐为double。这干扰了我们的判断。有关内存对齐的更多知识,可以去查找更专业的文章。

所以我们采用三个一样大小的数据,再打印Tom的大小:

class Cat
{
    private:
        int age;
        int length;
        int gender;
    public:
        void eat();
        void sleep();
};
Cat Tom;
cout<< sizeof(Tom)<<endl;

结果是12,刚好只是3个int的大小,没有用来存放类函数的空间。

由此可见,对象内不包含类函数。所有的类函数都如类里面的static数据一样,只不过前者存放于代码区,后者存放于全局区。而类内的static函数non-static函数的区别只在于,non-static函数隐含了一个指针参数,这个指针参数就是指向各个对象的this指针。

PS:关于这个机制,《Essential C++》内有一定论述,起初笔者以为是预处理为其加上了this指针的参数,但用g++ -E命令并没有得到想要的结果。本内容很多是基于笔者实测作出的推断,有错误的地方请联系笔者及时修改。

全局区

全局区内容有三:

  • 全局变量
  • 静态变量
  • 常量(const修饰的和字符串字面量
#include<iostream>
#include<string>
using namespace std;

//Global
string s{"Hello World"};
int i{9};

int main()
{
    //static
    static double pi{3.1415926};

    //Local
    unsigned j = 10;

    //constant, includes string literals and const variables.
    cout << "Literal:" << &"String" << endl;
    cout << "Global:" << &i << endl;
    cout <<"Global Object"<< &s << endl;
    cout << "Static:" << &pi << endl;
    cout << "Local:" << &j << endl;

    return 0;
}

在上述程序内,我演示了打印全局变量、对象还有静态变量和字符串字面量的内存位置。打印的具体数值可能因为设备、编译器不同而不同。在我vscode(gcc)中的结果是:

内存打印结果

0x代表16进制,可见全局量、静态量都在0x40xxx区域。而与之相比的局部变量则在0x61xxx段。

关于其他字面量存放位置,有待更专业的解答。

因此我们会发现,循环、函数内声明的static变量不会被反复初始化,也不会因为循环或者函数结束而被销毁。但其作用域却被限制在{}内,我们也不能在外面使用它。

而类内的static成员作用域也是类作用域,所以我们可以在类外初始化它,但不能在构造函数内初始化它

TIP:因为static变量不像堆内变量一样可以手动释放,它在程序结束才会自动释放,所以少使用它。

此外,static变量默认值都是0。

栈(stack)是一块由编译器自动管理的内存。内含函数参数局部变量,当参数或者变量不再使用,就会被自动销毁。

局部变量当然也包括main函数内声明的变量和循环内声明的变量。

因为这样的特性,所以不要返回局部变量的地址,编译器可能会考虑到你不懂这个特性,不马上销毁这个数据。但请不要这样做。

//不要像下面这样写代码
int* arr()
{
    int a[10]={1,2,3,4,5,6,7,8,9,10};
    return a;
}

此外,栈的大小有限,不要把太多数据放在栈中(比如int a[100][100][100]),栈溢出会给出警告。

点名某网站stackoverflow。

堆(heap)的特点就是大,但因为需要手动去管理内存,所以内存泄露常常发生在这里。

我们使用一些函数和关键字来管理堆内存。

//C-version
Type *p=(Type*)malloc(Type);
Type *pa=(Type*)malloc(num*sizeof(Type));

free(p);
free(pa);

//C++-version
Type *p{new Type()};
Type *pa{new Type[num]};

delete p;
delete[] p;

C中malloc函数返回void*类型,需要强制转换为需要的类型;而C++new关键字允许开辟内存同时初始化。虽然你的操作系统会在程序结束之后回收堆内存,但建议一开始就做好内存管理。

我们经常利用构造函数和析构函数完成内存管理。

class Game
{
public:
	Game();
	~Game();

	void gameStart();
	void gameRun();
	void gameEnd();

private:
    //我们把数据成员中体积大的对象都放入堆中,减小栈的压力。
	UI* start;
	Map* map;
	Snake* snake;
	Food* food;
	static Tools tool;
	unsigned speed;
};

Game::Game()//列表初始化
	:start(new UI{}), 
	map(new Map{}),
	snake(new Snake{}),
	food(new Food{ *map,'$' })
{}

Game::~Game()
{
	delete start;
	delete map;
	delete snake;
}

其他

除了以上内存四区,计算机还有一些区域可以存储数据。

寄存器

在C和C++中有一个前缀register,使用这个前缀可以声明一个寄存器变量。

register int var;

这样的变量会放在CPU的寄存器中,便于CPU使用。因此不要对这样的变量取地址。

但和inline关键字一样,register只能向编译器发出请求,最终由编译器决定是否把变量放在寄存器中。

磁盘

磁盘是存放文件的场所,你写的源代码,库,.exe都会存在磁盘中。可以通过文件读写操作把数据保存到磁盘。这里不介绍如何进行文件读写,有兴趣的读者可以去找其他文章。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值