这次的附加题推荐的博客是http://www.ruanyifeng.com/blog/2013/11/stack.html阮一峰的,感觉讲的深入浅出,比较适合对计算机刚刚接触的人;
下面谈谈感想:
这边文章主要讲了stack的三种理解:
第一个是咱们常说的用于算法的栈,这种栈结构非常适合做一些有意思的题,而且栈的思想最主要,先进后出。
第二个就是在函数层面的栈,这里指的就是在函数调用时候出现的栈,即每次调用生成一个新的数据空间来存放新的函数并赋初始值,而每次调用玩函数后则会释放掉函数的空间。
第三个就是在系统层面的栈,主要是通过对比栈和堆的概念来提出的,这里的栈指的不光是函数等而是整个代码的层次结构,这里面的栈主要强调的是数据大小的确定性和作用域的关系,即每出现一个新的作用域便会生成一个新的栈空间来存这个作用域的东西,并且前提是这个作用域的所用变量的大小都是已知的,相对于堆的不可知大小概念提出。此外还主要讲了进程和线程与栈的关系,即每个线程单独占用一个栈,但是可以分享堆里面的内容。
自己的理解:
实话说按我自己的理解,这三点讲的都是一点,只不过是栈的三种不同的应用而已,而且在这学期的编译课还重点讲了系统层次的栈,所以感觉内容还是比较适合初学者的,其核心思想都是后进先出,这边博客唯一增加的知识就是线程与栈的关系。但是这篇文章却没有解释具体的结构关系,比如父域生成一个线程,那么新生成的线程是如何开辟新空间的?是在原来的栈的基础上生成?还是在一个新开辟的占空间里面生成?这些作者都没有说。而且我还看了评论,大多数有经验的编程者都能发现该博客中的漏洞,比如不严谨的说法如堆的调用比栈慢,却没有详细的解释为什么?是操作系统的原因还是索引效率的问题还是cache与主存的速度导致的?都不得而知。所以个人觉得这边博客的意义不大。
对于上面的问题我自己上网进行的调查
参考http://blog.csdn.net/echoisland/article/details/6403763
结论如下:
1.首先解释新生成的线程如何与栈的具体关系:
首先我们知道线程是CPU在高速运行时不断切换其运行状态来让人们直观地认为是并发的但实际上是顺序执行的一种概念,而当系统切换线程的时候会自动切换线程的栈,也就是切换SS/ESP寄存器,这样我才想可能新线程是在新的栈生成一块区间的而不是在原来的栈
2.解释为什么栈会比堆运行的更快:
首先清理一下概念的关系,这里说的运行的更快不是指硬件层面的块,而是至设计层面的块。举个例子就像从北京到天津,同样的距离同样的交通工具,如果你走直线一定会比走去曲线来得更快,这就是我所说的设计层面的东西。
栈:
由于栈是有空间限制的,也是连续的,所以当程序调用栈的时候地址的顺序调用和顺序执行,即后进先出,1234按顺序进,但是无论怎么样都不会出现抽出的顺序是3124这就是我说的地址的顺序调用和顺序执行。
堆:
由操作系统的一个链表来存放空闲地址,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲 结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
读到这里想必你已经有谱了,如果你要给堆分配空间首先要做的是遍历链表,这一定会比栈按地址顺序来操作麻烦得多
此外不光设计模式的因素在还有硬件的因素,例如
void main() { char a = 1; char c[] = "1234567890"; char *p ="1234567890"; a = c[1]; a = p[1]; return; }
执行这样一段代码,我们看一看他相应的汇编代码
10: a = c[1]; 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 0040106A 88 4D FC mov byte ptr [ebp-4],cl 11: a = p[1]; 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 00401070 8A 42 01 mov al,byte ptr [edx+1] 00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符。从这一过程我们可以猜想到机组课上的原理,即第二种多一步的操作应该是把主存中的内容读取到缓存cache中以方便之后对此变量的快速操作。堆和栈虽然都是存放在内存中,但是我们知道内存也是分等级的越接近CPU的内存越快越小,所以我猜想堆和栈存放在不同等级的cache中,这也是他们读取速度不同的原因,因为读取堆的内容要钱把他传到更接近CPU一级的cache中这个过程会浪费一点时间。
说了这么多都是对堆和栈的深刻理解,要是一般编程的话需要理解的就是能知道大小的就由栈来存放,不知道大小的就由堆来存放。
有人可能问了
void main() { char a = 1; char c[] = "1234567890"; char *p ="1234567890"; a = c[1]; a = p[1]; return; }
这段代码为什么*p指向的内容是由堆存放的呢?
我们要明白对于c[]来说,编译器首先统计变量长度,然后存放到c[]的变量中,但是*p不同他是指针他需要知道的长度通过指针的类型即char就能确定所以指针是存放在栈中,而指针指向的内容的长度是事先不知道的,所以内容存放在堆中。
有人可能又问了这里的*p长度不是知道的吗?
没错这里面的长度是知道的,但是编译器处理的是通用的情况,一般人建立指针来对数据进行操作都是因为不知道空间大小,像这种情况完全可以用数组代替,大都市情况是这样的
char *p; p=(char*)malloc(sizeof(char)*n);
所以为了处理这种情况,编辑器不可能会先判断内容能不事先确定大小的,索性统一处理成不知道的样式。
个人理解,如有错误欢迎拍砖。一定及时纠正。