操作系统-04-程序员如何理解内存<下>

堆与栈的本质是什么

在编程语言中,堆区和栈区本质上都是内存,因此二者在本质上没有任何区别,只不过这两块内存的使用方式是不一样的。

在数据结构与算法中,我们也有堆和栈的概念,但那里指的不是内存,而是两种数据结构。

你可能会想,我们为什么要费尽心力的提出堆和栈这两个概念呢?之所以需要区分两种内存用法,根源在于:内存是有限的。

如果计算机内存是无限的,那么我们根本就不用这么麻烦的给内存划分两个区域,在其中的一个区域中这样使用内存,另一区域那样使用内存,这些都是不需要的。即使在今天PC内存普遍都在8G、16G,这依然是不够的,因此我们需要合理的来安排内存的使用,堆和栈就是为达到这一目的而采用的技术。

你会发现栈其实是一种非常巧妙的内存使用方法。函数调用完成后,函数运行过程中占用的内存就会被释放掉,这样,只要程序员代码写的合理(栈帧不至于过大),那我们程序就可以一直运行下去,而不会出现内存不足的现象。程序员在栈区不需要担心内存分配释放问题,因为这一切都是自动进行的。而如果程序员想自己控制内存,那么可以选择在堆上进行内存分配。因此这里提供了两种选择,一种是“自动的”,一种是“手动的”,目的都是在合理使用内存的同时提供给程序员最大的灵活性。

堆和栈是计算机科学中很优秀的设计思想,这种设计思想充分的体现了计算机如何合理且灵活的使用有限资源。

指针与引用

在各种编程语言中我们应该经常听到两个词,那就是引用或者指针。这两个词都是和内存相关的,指针和引用的作用都是“如何找到存放在内存上的数据”。

C/C++中有“指针”这样一个概念,而其它语言比如Java、Python有的只是“引用”这样一个概念。这两者有什么区别呢?我们打个比方你就能理解了。

“引用”就好比一个人的外号一样,就好有个程序员叫令狐冲,但是令狐冲同学在A公司的英文名可能是“Tom”,在B公司中可能又叫“Jerry”,那么在A公司中你只需要喊一声“Tom”就能找到令狐冲同学。

而“指针”强调的是位置,比如令狐冲在A公司的工位是“10排第二个”,在B公司中的工位是“8排第六个”,下班后回的位置在“中关村”。

这个例子当中的令狐冲同学就好比程序语言中的对象,令狐冲的各种外号就好比对象的引用,令狐冲当前所在的位置就好比对象的指针。

虽然通过“引用”和“指针”都能找到令狐冲同学,但是寻找的方式是不一样的。

只有C/C++这样的编译型语言才会有“指针”这样一个概念,指的是当前的对象放在了内存中的哪个位置上了。在比如Java、Python等语言中只有“引用”这样一个概念。在C/C++语言中,我们可以通过指针直接找到一个对象,因为你知道这个对象就在内存中指针所指向的位置,但在Java、Python等语言中,当你利用引用找到对象时基本上是冲着解释器喊一句“Hey,解释器,帮我找到令狐冲这个对象”,解释器通过记录查找到这个对象,注意解释器是知道对象在内存中的真正位置的,由于直接管理内存是一项非常繁琐容易出错的事情(C/C++程序员一定对此有深刻体会),因此解释器就接手了对内存直接管理,Java、Python等程序员是没有必要知道对象在内存中的真正位置的,没有指针也可以开心的写程序而且程序更加健壮,何乐不为呢,因此这些语言中是没有指针这样一个概念。

Sun的一篇论文中提到了为什么Java里没有指针。

Most studies agree that pointers are one of the primary features that enable programmers to inject bugs into their code. Given that structures are gone, and arrays and strings are objects, the need for pointers to these constructs goes away. Thus, Java has no pointer data types. …

You no longer have dangling pointers and trashing of memory because of incorrect pointers, because there are no pointers in Java.

大意是Java设计者认为指针太有技巧性以至于很容易出错,因此Java中没有指针。其实不只是Java,流行的语言当中除了C/C++之外几乎都没有指针。

在这一节中,你只需要理解以下两点就可以啦。

指针:直接在内存中找到变量所在位置。所以指针是实实在在的内存地址。

引用:告诉解释器你想使用的变量,然后解释器再去内存中找到变量的位置。所以引用只是解释器的一个承诺,只要这个变量存在,解释器就承诺能找到这个变量,程序员就可以使用这个变量,至于这个变量在内存中的什么地方是不需要程序员关心的。

进程的内存模型
我们已经在前面几个小节中研究了C/C++以及Java、Python程序的内存模型,接下来让我们回到操作系统。

我们已经知道了,不管什么语言,最后操作系统看到的都是C程序,C程序在内存运行起来就是进程。而在前面的小节当中我们已经知道进程在内存中的样子,但那里的描述其实是不完整的,也是不准确的。接下里我们就来看一下,操作系统中的进程在内存中到底是什么样子的,如下图所示(注意这幅图描述的是32位操作系统下进程在内存中是什么样子的),我们需要注意以下几点:
在这里插入图片描述
1.在上图中多出了一块内存,注意,这块内存就是操作系统在运行的时候所占用的内存。

2.每个进程独占一个连续的4G大小的内存,从内存地址0开始,一直到0xffffffff,其中最上方的1G留给了操作系统使用,下方的3G是留给进程自己使用的,其中程序员可以操作的区域就是图中的堆区和栈区。

3.你会发现代码段下方也有一点空隙没有使用,其实这是有特殊目的的,具体用途会在后面的章节中讲解。

现在你已经知道了进程在内存中的样子,你一定会有疑问吧,

为什么每个进程认为自己占用的是4G内存呢?如果我的PC上只有2G内存,进程还是认为自己拥有4G内存吗?

操作系统上不是可以同时运行很多进程吗,内存是有限的,假如只有2G,每个进程都认为自己拥有4G内存,这不会有问题吗?

我们首先来回答第一个问题:是的,每个进程都认为计算机上的真实内存就是4G,而且是进程自己独占的,即使真正的物理内存只有256MB。

第二个问题:很显然,不管你现在看这篇文章用的电脑,iPad,安卓手机还是iPhone,这些计算设备中的进程都是这么认为的,你能看到这篇文章说明进程认为自己拥有4G内存是不会出现问题的。

在这里需要再次强调的是:

每个进程都认为真实的内存就是4G,其中1G被操作系统使用,剩余部分被进程使用,也就是可以被程序员使用。注意这是不受真实物理内存限制的,也就是说,即使真实的物理只有256MB,进程同样认为在内存是4G,其中1G是操作系统的,剩余3G是进程自己独占的,程序员依然可以按照内存大小是3G来写程序。所以在大小256MB的真实物理内存上,程序员依然可以一次性申请超过256MB的内存而且可以申请成功,后续内存的使用也不受影响。

就像我第一次知道这种魔法时一样,你肯定也会惊呼这怎么可能呢?我们怎么能在256MB大小的内存上申请超过256MB的内存呢?但事实就是如此,你可以在物理内存大小为256MB的内存上面申请超过256MB的内存,而且无论物理内存大小,每个进程都认为自己拥有4G内存,而且是独占内存。

这真的是太神奇了,这就是本课程的主角-操作系统带来的神奇魔法。

幻象大师——操作系统
这种魔法确实是真实的,这个魔法就来自我们的幻象大师-操作系统,其实进程看到的内存是操作系统制造的幻觉。操作系统让每个进程都认为内存就只有两部分,一部分是操作系统的一部分是自己的,这种魔法就称之为虚拟内存。后面的章节中会重点介绍操作系统是如何实现这种魔法的。

本文来自《码农的荒岛求生》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值