一、预备知识—程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap)— 由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。
5、程序代码区— 存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
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"优化成一个地方。
接下来看看一个例子:
该例子描述的是print不带传参数的,要求在print中打印在调用函数中定义的一个数组序列的值。(要完成这要求只能先了解栈的知识,然后了解函数是如何被调用的,以及调用过程中都做些什么,以下具体描述),在VC6.0下编译:
#include <stdio.h>
void print()
{
//这里进行打印arr数组,print不准传参数
int *p,i;
unsigned int _ebp;
//该处为嵌入汇编
__asm{
mov _ebp,ebp
}
p=(int *)(*(int *)_ebp-4-4-4-4-8-7*4);
for( i=0;i<7;i++)
printf( "%d \n", p[i] );
}
int main()
{
int s;
int ss=0;
char *str="fdsafdsafdsafdsafdsafdsafdsa";
char fdsa='f';
char srt[8];
int arr[]={32,43,3,567,987,21,56};//数值随即
print();
return 0;
}
在VC6.0中的编译的汇编情况:
-- D:\VC_test\six\main.c
//main函数部分
注:ebp与esp的区别:esp是堆栈指针寄存器,程序运行时时浮动的,不利于暂存其需要以后用到的数据。所以这工作只号交给ebp来做了,这个是基址寄存器,长用来存放某些函数发生调用时候的esp寄存器中的值。
17: int main()
18: {
00401050 push ebp //保存调用main()函数的那个程序(一般为操作系统)的ebp中的值
00401051 mov ebp,esp //保存esp到ebp
以上这两句主要功能是为了保存程序调用main()时候的自身的状态信息,一边执行完main()之后可以返回程序并且恢复原来的状态
00401053 sub esp,74h //在main()函数中以调用函数的那个esp的值为基础分配一段74H空间大小的栈空间用于存放变量,以及调用函数相关的寄存器值(接下来的三个寄存器就是保存调用函数中的的信息ebx esi edi)
00401056 push ebx
00401057 push esi
00401058 push edi
00401059 lea edi,[ebp-74h]
0040105C mov ecx,1Dh
00401061 mov eax,0CCCCCCCCh
00401066 rep stos dword ptr [edi]
上面这四句话是把已经分配的74H的栈空间每个单元都初始化为0CCH
19: int s=0;
00401068 mov dword ptr [ebp-4],0
变量s 存放于栈空间地址ebp-4,以下同理
20: int ss=0;
0040106F mov dword ptr [ebp-8],0
21: char *str="fdsafdsafdsafdsafdsafdsafdsa";
00401076 mov dword ptr [ebp-0Ch],offset string "fdsafdsafdsafdsafdsafdsafdsa" (00422f8c)
str存放的是字符串常量的地址,而栈内也是放字符串常量的地址,其内容放于堆中(处于数据段中)
22: char fdsa='f';
0040107D mov byte ptr [ebp-10h],66h
23: char srt[8];
24: int arr[]={32,43,3,567,987,21,56};//数值随即
00401081 mov dword ptr [ebp-34h],20h
00401088 mov dword ptr [ebp-30h],2Bh
0040108F mov dword ptr [ebp-2Ch],3
00401096 mov dword ptr [ebp-28h],237h
0040109D mov dword ptr [ebp-24h],3DBh
004010A4 mov dword ptr [ebp-20h],15h
004010AB mov dword ptr [ebp-1Ch],38h
25://从上面汇编语句可以知道,对于数据,是在栈内先分出数组长度的空间,然后第一元素放于低地址,第二个元素放于次低地址,一次顺序。。。
26: print();
004010B2 call @ILT+0(_print) (00401005)
27: return 0;
004010B7 xor eax,eax
28: }
004010B9 pop edi
004010BA pop esi
004010BB pop ebx
004010BC add esp,74h
004010BF cmp ebp,esp
004010C1 call __chkesp (004010f0)
004010C6 mov esp,ebp
004010C8 pop ebp
004010C9 ret
//上面这几句是main()执行完了,返回调用程序时做的事情,恢复ebp esp 等
//print()子函数部分
--- D:\VC_test\six\main.c
1: #include <stdio.h>
2:
3: void print()
4: {
0040D4E0 push ebp
这语句很重要,把 ebp 的值( main()中分配的栈基地址 )压入栈中,此时esp中的值( 即存放ebp中的值的地址 )
0040D4E1 mov ebp,esp
//把主函数中ebp寄存器的值的地址送到print中的ebp寄存器
0040D4E3 sub esp,4Ch
0040D4E6 push ebx
0040D4E7 push esi
0040D4E8 push edi
0040D4E9 lea edi,[ebp-4Ch]
0040D4EC mov ecx,13h
0040D4F1 mov eax,0CCCCCCCCh
0040D4F6 rep stos dword ptr [edi]
5: //这里进行打印arr数组,print不准传参数
6: int *p,i;
//print()中的上面这几行作用于main()类似,每个被调用函数都会有这样的操作
7: unsigned int _ebp;
8: __asm{
9: mov _ebp,ebp
//取print中的ebp寄存器的值到整型变量_ebp中
这样的话_ebp就有这样的意思了:整型变量_ebp中的值为主函数main()中栈基地址的地址
0040D4F8 mov dword ptr [ebp-0Ch],ebp
10: }
11: p=(int *)(*(int *)_ebp-4-4-4-4-8-7*4);
/*
这句话可能有点难理解,我是这么理解的,变量名_ebp其实在内存中就是一个地址(地址也是一个整型数值),在这个地址中存放的是unsigned int 类型的数值。如今因为_ebp中的那个unsigned int类型的值代表的意思是另外一个数的地址值,所以呢为了取出这个地址值,我们应该怎么做呢?我们可以这样做:
把_ebp的类型转换为int *_ebp的整型指针类型,如此一来这个_ebp中的值的意思就变成了一个特定值的地址,而我们为了得到这个特定值,可以通过指针操作符*来取出这个特定值,如*(int *_ebp) 。
*/
0040D4FB mov eax,dword ptr [ebp-0Ch]
0040D4FE mov ecx,dword ptr [eax]
0040D500 sub ecx,34h
0040D503 mov dword ptr [ebp-4],ecx
12: for( i=0;i<7;i++)
0040D506 mov dword ptr [ebp-8],0
0040D50D jmp print+38h (0040d518)
0040D50F mov edx,dword ptr [ebp-8]
0040D512 add edx,1
0040D515 mov dword ptr [ebp-8],edx
0040D518 cmp dword ptr [ebp-8],7
0040D51C jge print+57h (0040d537)
13: printf( "%d \n", p[i] );
0040D51E mov eax,dword ptr [ebp-8]
0040D521 mov ecx,dword ptr [ebp-4]
0040D524 mov edx,dword ptr [ecx+eax*4]
0040D527 push edx
0040D528 push offset string "%d \n" (0042201c)
0040D52D call printf (0040d770)
0040D532 add esp,8
0040D535 jmp print+2Fh (0040d50f)
14:
15:
16: }
0040D537 pop edi
0040D538 pop esi
0040D539 pop ebx
0040D53A add esp,4Ch
0040D53D cmp ebp,esp
0040D53F call __chkesp (004010f0)
0040D544 mov esp,ebp
0040D546 pop ebp
0040D547 ret
注:这是本人的一些理解,不对的地方还望各位同道人士指点指点。