您的位置:首页 > 编程语言 > C语言/C++

C语言:关于程序运行时的内存管理

2016-04-30 08:18 323 查看
  我们学习C语言时,不要只仅仅只会写代码,我们要去了解一些更底层的知识,去明白机器在我们按下编译,执行按钮后干了什么,明白这些对我们以后的学习更有帮助以及在处理一些问题为我们提供思路。

我们知道源文件在经过编译链接之后生成了目标文件,在映射完成后就开始执行,接下就是我们要谈的运行时内存管理。

有这么一道曾是腾讯面试时的题:

void add( void )

{

     int tmp = 0;

     int *p = (int *)*( &tmp + 1 );

     *(p - 1) = 20;

}

 

 

int main()

{

   int a = 10;

   add( );

   printf("%d",a);

   return 0;



那么最后打印的结果是多少那?似乎一眼看去你会回答是10,但很遗憾正确答案是20,你或许会疑惑为什么a的值会改变,因为程序里似乎没有一个代码是给a直接赋值或间接赋值,接下来我会简单的讲解为什么a的值会改变。

程序在运行时所有的局部变量和函数都在栈上开辟内存,其他请求内存的函数如malloc等在堆上开辟,一般来说对的空间比栈大得多,栈的接口为esp,ebp寄存器,esp指向栈顶,ebp指向栈尾。用户可以向栈里面压入数据(push指令)每压入一个数据esp向上移动,也可已取出一个数据(pop指令),但是栈必须遵守一个规则,那就是先进后出。

这里要提出:栈保存类一个函数调用所需要的维护信息或活动记录





注意:地址从上而下为低到高

我们来讲解一个简单的程序来说明运行时内存管理:

#include

 

int add( int x, int y )

{

 int tmp = 0;

 tmp = x + y;

 return tmp;

}

int main()

{

 int a = 10;

 int b= 20;

 int c = 0;

 c = add( 10, 20 );

 printf("%d",a );

 return 0;

}

我们按下f10按钮进行调试,查看它的反汇编代码:

int main()

{

008313F0  push        ebp           push指令把ebp的值压入栈中,这个ebp的值是调用main函数函数的ebp,我所用的是vc2010,这个函数为main_CBTN,同时esp的值减少即esp的值向上移动,

008313F1  mov         ebp,esp       把esp的值赋给ebp,

008313F3  sub         esp,0E4h      将esp的值向上移动0e4h。

008313F9  push        ebx           将寄存器ebx的值压入栈中

008313FA  push        esi           将寄存器ebx的值压入栈中

008313FB  push        edi           将寄存器ebx的值压入栈中

008313FC  lea         edi,[ebp-0E4h]  接下来的四个步骤是初始化工作,把ebp到ebp-0e4h的值初始化为0cccccccc.

00831402  mov         ecx,39h 

00831407  mov         eax,0CCCCCCCCh 

0083140C  rep stos    dword ptr es:[edi]

 附图:

 


 

int a = 10;

0083140E  mov         dword ptr [ebp-8],0Ah  将10即0a(16进制)放在ebp-8到ebp这段内存里

 int b= 20;

00831415  mov         dword ptr [ebp-],14h  同上一步

 int c = 0;

0083141C  mov         dword ptr [c],0  同上一步

 附上内存图:ebp的值为0x0018fee4





记下来的是调用函数的汇编代码:

 c = add( 10, 20 );

00831423  push        14h                         把20(16进制为14)压入栈中

00831425  push        0Ah                         把10(16进制为a)压入栈中

                                     上面所压入的值就是add函数的形参

00831427  call        00831091                   

0083142C  add         esp,8 

0083142F  mov         dword ptr [ebp-20h],eax 

 printf("%d",a );

在上面的第四行代码是跳转指令(这是一个近址相对位移调用指令),调用add函数,并且把下条指令的地址值压入栈中,跳转后的指令为:

int add( int x, int y )

{

008313A0  push        ebp 

008313A1  mov         ebp,esp 

008313A3  sub         esp,0CCh 

008313A9  push        ebx 

008313AA  push        esi 

008313AB  push        edi 

008313AC  lea         edi,[ebp+FFFFFF34h] 

008313B2  mov         ecx,33h 

008313B7  mov         eax,0CCCCCCCCh 

008313BC  rep stos    dword ptr es:[edi] 

 int tmp = 0;

008313BE  mov         dword ptr [ebp-8],0 

 tmp = x + y;

008313C5  mov         eax,dword ptr [ebp+8] 

008313C8  add         eax,dword ptr [ebp+0Ch] 

008313CB  mov         dword ptr [ebp-8],eax 

 return tmp;

008313CE  mov         eax,dword ptr [ebp-8] 

}

上面的代码指令与main函数开辟空间时基本相同,不一样的是下面的返回指令

008313D1  pop         edi 

008313D2  pop         esi 

008313D3  pop         ebx 

008313D4  mov         esp,ebp 

008313D6  pop         ebp 

008313D7  ret 

不过我们现在不关心这些,附上图:





通过上面的解释我们就能理解开始的那道题了,通过对add函数中变量的地址位移可以取得main函数ebp的值,进而可以对main函数中变量的值进行间接赋值。

由于我所使用的是vc2010系统,ebp与变量的空间,变量与变量的空间会有间隔,所以用开始的那道题进行操作推理时得不出答案(vc2008及以前的系统是可以实现的),事实上vc2010的系统更加严格,它会因为一个非法引用直接报错。

 

 

  

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内存管理 c语言