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的系统更加严格,它会因为一个非法引用直接报错。
我们知道源文件在经过编译链接之后生成了目标文件,在映射完成后就开始执行,接下就是我们要谈的运行时内存管理。
有这么一道曾是腾讯面试时的题:
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 语言程序(二)
- 如何写好 C main 函数
- Lua和C语言的交互详解
- Lua的内存管理浅析
- 关于C语言中参数的传值问题
- 简要对比C语言中三个用于退出进程的函数
- 深入C++中API的问题详解
- 基于C语言string函数的详解
- C语言中fchdir()函数和rewinddir()函数的使用详解
- C语言内存对齐实例详解
- C语言编程中统计输入的行数以及单词个数的方法
- C语言自动生成enum值和名字映射代码
- 使用C语言判断英文字符大小写的方法
- c语言实现的带通配符匹配算法
- C语言实现顺序表基本操作汇总
- C语言中计算正弦的相关函数总结
- 使用C语言详解霍夫曼树数据结构
- C语言实现选择排序、冒泡排序和快速排序的代码示例
- 探讨C语言的那些小秘密之断言
- C语言实现BMP转换JPG的方法