簇: 数据存储在硬盘的时候都是以簇位单位,所以无论文件大小是多少,除非正好是簇大小的倍数,否则文件所占用的最后一个簇或多或少都会产生一些剩余的空间,且这些空间又不能给其它文件使用,即使这个文件只有0字节,也不允许两个文件或两个以上的文件共用一个簇,不然会造成数据混乱。
程序运行的过程:代码->编译(.obj)->链接(.lnk)->可执行程序(.exe/.hex…)
编译属于初加工,类似于宰杀猪的过程
链接则属于深加工,类似于将生猪肉做成一盘菜的过程
编译器的本质, 其实就是将一些复杂逻辑分解成一些简单逻辑。
堆,栈,全局区,常量区,代码区(计算机分配这些区域是为了使用的安全,所以为不同的内存区域设置不同的权限)
堆(可读可写): 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。需要自己手动释放。
栈(可读可写):每个程序运行的时候都会申请一块4GB的内存空间,其中默认生成栈的大小为1024KB。。栈空间不需要在高级语言里面显式的分配和释放。
//这段代码便会出错,发生溢出
int main ()
{
char strBuffer[1024 * 1024] = { 0 };
return 0;
}
//可以修改为
int main()
{
char* strBuffer=new char[1024*1024];
return 0;
}
/*即将数据从栈区转到堆里面,在使用完之后要记得释放空间(delete strBuffer)
*/
全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域, 程序结束后由系统释放 。
常量区(只读不写):常量字符串就是放在这里的。 程序结束后由系统释放 。
代码区(可读可写):存放函数体的二进制代码。
int main()
{
00A62280 push ebp //将ebp压入堆栈
//ebp:0030f8d8 esp:0030f8c4
00A62281 mov ebp,esp //将ESP的值赋给ebp,也就意味着此时堆栈的栈顶和栈底是一样的。
//ebp:0030f8c4 esp:0030f8c4
00A62283 sub esp,0C0h //提升栈顶,为后续操作腾出地方
//ebp:0030f8c4 esp:0030f804
00A62289 push ebx
//ebp:0030f8c4 esp:0030f800
00A6228A push esi
//ebp: 0030f8c4 esp:0030f7fc
00A6228B push edi //保护现场
//ebp:0030f8c4 esp:0030f7f8
00A6228C lea edi,[ebp-0C0h] //将此时栈顶的地址放入edi中,为后续操作做准备
//ebp: 0030f8c4 esp:0030f7f8
00A62292 mov ecx,30h //次数 30
//.....
00A62297 mov eax,0CCCCCCCCh
//..
00A6229C rep stos dword ptr es:[edi] //重复填充cccccccc操作30(H)次
printf("I love POEDU!!");
//ebp:0030f8c4 esp:0030f7f4
00A6229E push offset string "I love POEDU!!" (0A66CB0h) //将“I love poedu”压入堆栈
//ebp:0030f8c4 esp:0030f7f0
00A622A3 call _printf (0A6134Dh) //调用printf参数
//....
00A622A8 add esp,4 //内平栈
//ebp:0030f8c4: esp:0030f7f4
return 0;
00A622AB xor eax,eax // return 0
}
00A622AD pop edi
//ebp:0030f8c4 esp:0030f7f8
00A622AE pop esi
//ebp: 0030f8c4 esp:0030f7fc
00A622AF pop ebx //恢复现场
//ebp: esp:0030f800
00A622B0 add esp,0C0h //降低堆栈
//ebp:0030f8c4 esp:0030f8c4
00A622B6 cmp ebp,esp // 比较栈顶和栈底
00A622B8 call __RTC_CheckEsp (0A61334h) //调用 __RTC_CheckEsp检查堆栈空间
00A622BD mov esp,ebp
00A622BF pop ebp //清理工作
00A622C0 ret
函数如何传参:
首先提升堆栈空间为参数腾地方,在这一过程中要保证运行当前指令前的其它参数不变(因为在执行完当前函数后可能还要继续其它的操作),所以要保护现场。
参数一开始是在内存中,在调用函数之前需要将参数传递到堆栈中,而后才会调用函数。
以下是关于堆和栈的详细区别,引用自网络,原文请见 shine_yr的博客
栈(stack)和堆(heap)具体的区别。
(1)在申请方式上,
栈(stack):现在很多人都称之为堆栈,这个时候实际上还是指的栈。它由编译器自动管理,无需我们手工控制。 例如,声明函数中的一个局部变量int b 系统自动在栈中为b开辟空间;在调用一个函数时,系统自动的给函数的形参变量在栈中开辟空间。
堆(heap): 申请和释放由程序员控制,并指明大小。容易产生memory leak。
在C中使用malloc函数。
如:p1 = (char *)malloc(10);
在C++中用new运算符。
如:p2 = new char[20];//(char *)malloc(10);
但是注意p1本身在全局区,而p2本身是在栈中的,只是它们指向的空间是在堆中
(2)申请后系统的响应上,
栈(stack):只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆(heap): 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete或free语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
(3)申请大小的限制
栈(stack):在Windows下,栈是由高向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
堆(heap): 堆是向高地址扩展的数据结构,是不连续的内存区域(空闲部分用链表串联起来)。正是由于系统是用链表来存储空闲内存,自然是不连续的,而链表的遍历方向是由低地址向高地址。一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。由此可见,堆获得的空间比较灵活,也比较大。
(4)分配空间的效率上
栈(stack):栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。但程序员无法对其进行控制。
堆(heap):是C/C++函数库提供的,由new或malloc分配的内存,一般速度比较慢,而且容易产生内存碎片。它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。这样可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。显然,堆的效率比栈要低得多。
(5)堆和栈中的存储内容
栈(stack):在函数调用时,第一个进栈的是主函数中子函数调用后的下一条指令(子函数调用语句的下一条可执行语句)的地址,然后是子函数的各个形参。在大多数的C编译器中,参数是由右往左入栈的,然后是子函数中的局部变量。注意:静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中子函数调用完成的下一条指令,程序由该点继续运行。
堆(heap):一般是在堆的头部用一个字节存放堆的大小,堆中的具体内容有程序员安排。
(6)存取效率的比较
这个应该是显而易见的。拿栈上的数组和堆上的数组来说:
void main()
{
int arr[5]={1,2,3,4,5};
int *arr1;
arr1=new int[5];
for (int j=0;j<=4;j++)
{
arr1[j]=j+6;
}
int a=arr[1];
int b=arr1[1];
}
上面代码中,arr1(局部变量)是在栈中,但是指向的空间确在堆上,两者的存取效率,当然是arr高。因为arr[1]可以直接访问,但是访问arr1[1],首先要访问数组的起始地址arr1,然后才能访问到arr1[1]。