栈与堆

​ 昨天参加了Pwn师傅的入门课,嗯,<(我以为大家都是零基础,结果最后发现只有我才是).png>,(传图好麻烦,偷点懒。)二进制安全对底层的知识要求还是挺高的,也难怪pwn入门难度是最高的233333。今天稍微学习了一下堆和栈的区别,写点笔记。

程序的内存分配

一般来说,由c/c++编译的程序(划重点:已经编译!)要占用的内存分为以下几个部分:

1.栈区(stack):一般由系统自动分配释放、存放函数参数值、局部变量值等,操作方式有点类似于数据结构中的栈(然而要复杂的多,一开始pwn师傅问我们懂不懂栈的时候我还以为是数据结构中的栈,尴尬.png)

2.堆区(heap):一般由程序员自主分配和释放,没有释放的话OS有可能会回收,而且和数据结构中的堆区别很大。

3.全局(静态)区(static):初始化的全局变量和静态变量会放在同一块区域,未初始化的则会放在相邻的另一块区域,程序运行结束后空间由系统释放。

4.常量区:比如字符串。程序运行结束后空间由系统释放。

5.代码区:即函数的二进制代码。

示例:(这例子貌似被引用了千万次了23333)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[] = "abc";// 栈
char *p2; 栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c =0//全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}

堆与栈的比较

申请方式

栈:系统自动分配。比如在函数里面声明局部变量 int i;

堆:程序员自行分配,并指定大小。c中的malloc , c++中的 new

申请后系统的响应

栈:只要剩余空间大于申请空间,系统即为其提供相应内存,否则会异常。

堆:这里又有一个知识点:OS有一个记录空闲内存的链表,收到堆空间申请时会开始遍历这个链表,找到一个空间大于所要申请的空间后便把这个结点从空闲结点链表里面删掉并将这块空间分给程序,而且大多数系统会自动记录这块空间的首地址以便于正确释放这块空间。多余的空间则会返回给空闲链表。

申请大小的限制

栈:在win下,栈的地址是往低地址扩展的,而且是连续的内存区域,所以栈顶的地址以及栈的最大容量,系统都已经固定好了,无法修改,因此栈能够获得的空间较小。

堆:与栈相反,堆是往高地址扩展的数据结构,不连续,显然,因为这是系统在空闲链表里面找到的内存,然后分给你的,当然是各个地方的空间都有。所以堆获得的空间比较灵活,空间大小也就更大了。

申请效率

栈:由系统自动分配,速度比较快,但无法控制。

堆:自行分配,一般来说速度比栈要慢,而且很容易产生内存碎片, 但用起来最方便。

存储内容

栈:函数调用时,最先进栈的是主函数中后的下一条指令的地址,接着是函数的参数,大部分c的编译器中,参数都是从右到左入栈的,接着是函数的局部变量,但静态变量不会入栈。当函数开始调用时,这时就体现遵循后进先出的原则了,局部变量最先出栈,然后是参数,最后是栈顶指针存的地址,也就是主函数中的下一条指令,程序从这里继续运行。

堆:一般,堆的头部会用一个字节来存放堆的大小,具体由程序员安排。

存取效率

示例代码:

1
2
3
4
5
6
7
8
9
10
#include
int main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return 0;
}

c[]在运行时刻赋值,而*p在编译时确定,但在以后的存取中,栈上的数组比指针指向的字符串要快。具体原理自己看一下汇编的代码就清楚了。

小结

使用栈就像去餐馆吃饭,只要点菜(申请)和吃(使用),吃饱了就可以走人,不必理会切菜、洗菜什么的细节,快捷方便,但是自由度小。

使用堆就是自己动手做了,比较麻烦,要顾细节,但是自由度大。

感想

​ pwn的门槛真心比web高了不少,得恶补基础知识才行,都大二的人了,还啥都不懂,怪不得是鶸字辈。

参考出处:一条鱼@博客园