学习C语言,绝对逃不过内存这个问题,这篇博文便是我的C语言的内存使用的学习笔记
C语言的内存用来干啥了?
首先研究内存,我们就要搞清楚C语言程序用内存干了一些什么,一个经过编译的C语言程序占用的内存分为以下的几个部分:栈区,堆区,全局区,文字常量区以及程序代码区,接下来我们便一一来研究一下这些部分的具体作用
栈区
首先排名第一的就是栈区,那么,什么是栈区呢?所谓栈,是一种特殊的存储结构,只能从一端进行操作(限定仅在表尾进行插入或删除的线性表),即后进先出的一种储存结构,如下入所示
栈区用于以下的方面:当C语言函数调用的时候(其实整个C语言函数就是一个巨大的main函数),C将函数的各个参量以及临时变量等存到栈中,而栈底下便是这条函数调用后所应该调用的下一条指令
堆栈一般涉及到以下几种操作:
压栈(入栈)(push):即往栈顶添加一个元素
弹栈(出栈)(pop):即删除栈顶上的那一个元素
返回栈顶元素(top):即读取栈顶的那一个元素
检查栈是否为空(IsEmpty):看看栈是不是空的
检查栈是否已满(IsFull):看看栈是不是满的。
在大部分C语言编译器中函数中的参数是由右边到左边依次入栈的,而且栈能分配的空间不是特别的大
堆区
C语言的堆区就是用于存放在程序运行时被动态分配的内存段的区域,我们常用的malloc()函数分配的内存就是分配的这里的。堆的申请需要我们自己手动利用函数来进行操作
栈和堆小小的对比
那么我们就要对比以下堆于栈的区别了
区别 | 大小 | 是否由系统自动分配 | 分配速度 | 程序员是否可操作 |
栈 | 小 | 是 | 快 | 不可 |
堆 | 大 | 否 | 慢 | 可 |
全局区
全局区,又命名为静态区,其在编译过程中便已经由编译器分配好了,我们在程序中定义的全局变量以及我们定义的静态变量就储存在此处
文字常量区
用于储存程序运行的时候的常量,这些常量是不匀许进行更改的
程序代码区
存放我们写的程序的代码的地方。
一个简单的例子
#include<stdio.h>
int d = 0; //这个是全局变量,存放在全局区(静态区)
int a_plus_b(int a, int b)
{
static int c; //静态变量,存放在全局区(静态区)
c = a + b; //局部变量,储存在栈中
return c; //局部变量,储存在栈中
}
int main()
{
int a = 3; //局部变量,储存在栈中
int b = 4; //局部变量,储存在栈中
int c = 0; //局部变量,储存在栈中
c = a_plus_b(a, b);
printf("a + b=%d", c);
char* p = "可莉是最可爱的"; //*p 在栈上, "可莉是最可爱的"储存在常量区
int* p2 = (int *)malloc(4); //分来的4个字节储存在堆中
return 0;
}
关于缓冲区溢出(栈溢出)
讲到C语言的内存,我们就不得不提到C语言经典的安全问题:缓冲区溢出问题(又名为栈溢出的问题),
首先,C语言并不会检查数组啊变量啊等的大小,假如输入的值太大,可能就会超出本来这个变量能够承载大小,然后剩下的值就会“溢出”来,填充到栈中相邻的值处。
但是根据上面的学习,我们发现C语言栈中栈底存放的是函数结束后应该执行的下一条指令。那么,有没有可能出现这样一种情况:程序在服务器上以一个非常高的权限来运行,其通过scanf函数获取一个输入,一个人输入了一个非常大的数据,造成了溢出,而这个数据恰巧将栈底的函数运行完该执行的下一条指令的指向给覆盖掉了,而且覆盖的值刚好是指向另一串代码的指向,更不巧的是那一串代码是一个让服务器安装并运行rootkit的代码。哦吼,完蛋了。
所以说写代码中尽量不要使用scanf()等函数,会造成溢出问题,解决方案高版本的编译器一般都会有提示比如换成安全版本的函数scanf_s()