计算机系统基础学习小笔记(0)
深入理解计算机系统对我们有什么好处呢?
不少人在最初学C语言的时候,对这个语言的很多内容感到迷惑不解,比如说C语言的灵魂:指针。当然,也有不少人在使用C/C++时,遇到过一些非常奇怪的运行崩溃问题,比如内存访问越界,爆栈等。这个时候,如果我们没有了解过任何有关计算机运行原理的内容,这些奇怪的问题就会像一团乌云一样,一直漂浮在我们脑海上空。
如果我们能够更加深入理解计算机的结构,如果我们能够更加清楚地知道一个程序是如何在计算机上运行的,那么在面对形形色色的问题时,我们就能够更加容易地抓住问题的根源,从而解决问题。
这一系列的笔记是在看南大袁春风教授的计算机系统基础网课时进行记录的,以后要是忘了可以回来再看看(捂脸 )。
上面提到过,在写C/C++程序的时候,经常会遇到非常奇怪的问题,下面还有一些非常有趣的例子,来源于袁教授的课程:
这个情况就涉及到编译器对程序的优化问题,第一段的代码,除以-1被优化成取负指令neg,没有发生除法溢出,而第二种写法中,这样的写法让编译器没有采取优化除以-1为取负的方式,而是老老实实使用除法指令,不出意外地出现了SIGFPE。
(变量如果加了voletile修饰,则会从内存中重新装载内容,而不是直接从寄存器中拷贝内容)
这个例子在之后pwn的栈溢出中会体现出来,而且在不同位的机器下,情况也未必相同,这个例子需要我们了解栈帧中数据的布局。这里d[1]和a[2]的空间有可能相连,然后在调用的时候,a[i]被改变为1073741824,而从代码中不难看出a总共就2个空间,但是在调用了2,3,4的时候,会发现返回的d[0]值居然也发生了改变,这说明d[0]的位置就在a数组后面不远处,在调用了fun(4)的时候,发生了保护错,这是程序的一种安全防护措施,防止人们通过漏洞利用对其后空间的关键数据(比如函数返回值)篡改,从而改变程序的运行走向。(而这种改变函数返回值的方法也是栈溢出漏洞利用的基本操作)
这个例子也非常有趣,从代码表面来看,功能和时间空间复杂度完全一样,但是真正运行的时候,两者的速度却差了20倍还多,要理解这个问题,就必须要了解数组(尤其是高维数组)是如何在内存中存放的。内存空间在计算机中永远都是一维线性的,那么高维数组要想存储,就必须“降维”,拉直成一维连续空间,不同的语言对数组的存储方式也不一样,C语言采取了行优先,这就导致数组的存储在一维表示下是a[0][0],a[0][1],…,a[1][0],a[1][1],…,…,a[n][n],这一点会导致后续在访问空间的时候,行优先的遍历方式更加快速。那么你也许会疑惑,就算列优先遍历,咱访问空间,还是得老老实实按照下标[i][j]计算得来,怎么行优先就快了呢?这还需要我们了解cache机制和访问局部性……总之,要慢慢来了解。
这个例子也是指令的差别,众所周知,在printf中,数据如果和格式化字符串不相匹配,有可能会输出意料之外的内容,这个就是典型的情况,很显然下面的double(a)是正常的,那么double *p=(double*)&a;这样的强制转换,怎么就输出了0呢?这就是在编译阶段,前者使用的是fldl指令,而后者是fildl,fldl指令在这里,就是把a所在空间的01序列,直接用浮点数的方式打印出来,而fildl指令是将01序列转换为int型数据,再打印出来,指令上一个字母的差距,功能相差了十万八千里。
课程目标
冯诺依曼结构及其主要思想
结构介绍完之后,认识一下计算机最基本的部件
程序计数器是程序能顺序执行的一个关键部件,通过PC(program counter)和MAR(memory address register),可以从存储器中获取程序段(包括指令和操作数)通过MDR(memory data register),放入IR(instruction register)和GPRs,由控制器等核心部件,完成指令执行和运算。具体过程如下图:
图片里讲的非常清楚(膜拜袁教授)
下次还会水一些计算机系统概述的内容(跑)
查看之后内容
计算机系统基础学习小笔记(1)