C基础----返回局部变量的地址&栈(linux)
2012-10-15 23:01
239 查看
近来是校园招聘高峰期啊... ...
局部变量之局部指的是一个变量的作用范围,例如一个函数内定义的变量。这里我们要说的就是这种变量。
很多书上或者说面试宝典呐之类的,都说不要返回一个局部变量的地址以供外部使用。怎么说呢,这种说法应该是基于编码习惯软件工程角度来说的,这种做法确实很容易出问题,但不是一定会出问题。下面我们来看一个简单的C代码:
函数foo_b更简单,仅仅是在函数体内定义了一个变量b,其值为0xffff。
问题是,两次printf的输出是什么?答案是:
疑问1:为什么第一次说出的值是0xff?
第一个输出的是0xff,这个绝对不是巧合,这个值是foo_a留下的。这就像昨晚下了一场大雪,一大早的你出去踩新雪了,留下了很多脚印。如果,在你去过之后没有人再去的话,那么那些脚印还是在那的,除非雪化了!
我们先来看看main函数的反汇编结果,然后我们把每一步执行后的结果画出来。
main函数的栈帧目前只能看到指针p,其值为NULL,即0(可以找一下#define NULL 0;)。
call foo_a的时候会默认把下一条指令的地址入栈,从上面的反汇编推测应该是0x0804841B。call同时会将改变pc的值,应该是0x080483e4,即函数foo_a的地址,也就是说进入函数foo_a执行。
下面我们把函数foo_a的反汇编代码,并把执行结果画在图上:
对照上面的汇编,我们把foo_a执行以及执行后的栈的情况画出来。
汇编代码执行到lea -0x4(%ebp), %eax后,其实返回值已经被设置,函数的功能已经实现。后面就是函数的返回要做的事情。
leave指令相当于:movl %ebp %esp和pop %ebp。这两条执行和函数开始出的push %ebp和%movl %esp %ebp是相对应的。执行完这两条指令后,ebp和esp的值均被更改为进入foo_a时的值。也就是说foo_a的栈帧在逻辑上不在存在。然而foo_a的栈帧中的数据并不会被释放,也就是说栈空间(虚拟区域,vma)映射的物理内存这时候不会被回收。那么,存储在物理内存上的数据当然是在那的。就像前面打的比喻,你的脚印仍然在雪中,除非雪化了(物理内存被回收)。
那么,为什么函数返回时,那个函数的栈帧对应的物理内存不会被回收?其实我们可以反问一下,怎么被回收?怎么去触发回收?这里你是找不到回收的依据的。
所以说,foo_a返回后,输出的*p的值是0xff。
前面我们依据汇编画了foo_a的栈结构,下面我们简要的把foo_b的反汇编和栈结构画出来:
也就是说,函数foo_b执行完成以后把foo_a中a的地址处的值置成了0xffff。所以说,foo_b返回后,输出的*p的值是0xffff。
以上代码使用gcc 无优化编译连接,反汇编使用objdump -D。
如有错误请不吝赐教,谢谢!
局部变量之局部指的是一个变量的作用范围,例如一个函数内定义的变量。这里我们要说的就是这种变量。
很多书上或者说面试宝典呐之类的,都说不要返回一个局部变量的地址以供外部使用。怎么说呢,这种说法应该是基于编码习惯软件工程角度来说的,这种做法确实很容易出问题,但不是一定会出问题。下面我们来看一个简单的C代码:
1 #include <stdio.h> 2 3 int* foo_a() 4 { 5 int a = 0xff; 6 return &a; 7 } 8 9 void foo_b() 10 { 11 int a = 0xffff; 12 } 13 14 int main() 15 { 16 int* p = NULL; 17 18 p = foo_a(); 19 printf("0x%x\n", *p); 20 21 foo_b(); 22 printf("0x%x\n", *p); 23 24 return 0; 25 }这段代码所表达的意思是很清晰的,我们在函数foo_a中定义了一个变量a,其值为0xff,然后将其地址返回,注意返回的是变量a的地址。
函数foo_b更简单,仅仅是在函数体内定义了一个变量b,其值为0xffff。
问题是,两次printf的输出是什么?答案是:
0xff 0xffff如果答案没有出乎你的所料,那么下面你可以不用看了 :) 否则,请看下去。
疑问1:为什么第一次说出的值是0xff?
第一个输出的是0xff,这个绝对不是巧合,这个值是foo_a留下的。这就像昨晚下了一场大雪,一大早的你出去踩新雪了,留下了很多脚印。如果,在你去过之后没有人再去的话,那么那些脚印还是在那的,除非雪化了!
我们先来看看main函数的反汇编结果,然后我们把每一步执行后的结果画出来。
331 08048405 <main>: 332 8048405: 55 push %ebp 333 8048406: 89 e5 mov %esp,%ebp 334 8048408: 83 e4 f0 and $0xfffffff0,%esp 335 804840b: 83 ec 20 sub $0x20,%esp 336 804840e: c7 44 24 1c 00 00 00 movl $0x0,0x1c(%esp) 337 8048415: 00 338 8048416: e8 c9 ff ff ff call 80483e4 <foo_a> 339 804841b: 89 44 24 1c mov %eax,0x1c(%esp) 340 804841f: 8b 44 24 1c mov 0x1c(%esp),%eax 341 8048423: 8b 10 mov (%eax),%edx 342 8048425: b8 30 85 04 08 mov $0x8048530,%eax 343 804842a: 89 54 24 04 mov %edx,0x4(%esp) 344 804842e: 89 04 24 mov %eax,(%esp) 345 8048431: e8 ca fe ff ff call 8048300 <printf@plt> 346 8048436: e8 bb ff ff ff call 80483f6 <foo_b> 347 804843b: 8b 44 24 1c mov 0x1c(%esp),%eax 348 804843f: 8b 10 mov (%eax),%edx 349 8048441: b8 30 85 04 08 mov $0x8048530,%eax 350 8048446: 89 54 24 04 mov %edx,0x4(%esp) 351 804844a: 89 04 24 mov %eax,(%esp) 352 804844d: e8 ae fe ff ff call 8048300 <printf@plt> 353 8048452: b8 00 00 00 00 mov $0x0,%eax 354 8048457: c9 leave 355 8048458: c3 ret对照上面的汇编,我们画一个图。这一步我们只搞到函数foo_a调用之前。
main函数的栈帧目前只能看到指针p,其值为NULL,即0(可以找一下#define NULL 0;)。
call foo_a的时候会默认把下一条指令的地址入栈,从上面的反汇编推测应该是0x0804841B。call同时会将改变pc的值,应该是0x080483e4,即函数foo_a的地址,也就是说进入函数foo_a执行。
下面我们把函数foo_a的反汇编代码,并把执行结果画在图上:
314 080483e4 <foo_a>: 315 80483e4: 55 push %ebp 316 80483e5: 89 e5 mov %esp,%ebp 317 80483e7: 83 ec 10 sub $0x10,%esp 318 80483ea: c7 45 fc ff 00 00 00 movl $0xff,-0x4(%ebp) 319 80483f1: 8d 45 fc lea -0x4(%ebp),%eax 320 80483f4: c9 leave 321 80483f5: c3 ret
对照上面的汇编,我们把foo_a执行以及执行后的栈的情况画出来。
汇编代码执行到lea -0x4(%ebp), %eax后,其实返回值已经被设置,函数的功能已经实现。后面就是函数的返回要做的事情。
leave指令相当于:movl %ebp %esp和pop %ebp。这两条执行和函数开始出的push %ebp和%movl %esp %ebp是相对应的。执行完这两条指令后,ebp和esp的值均被更改为进入foo_a时的值。也就是说foo_a的栈帧在逻辑上不在存在。然而foo_a的栈帧中的数据并不会被释放,也就是说栈空间(虚拟区域,vma)映射的物理内存这时候不会被回收。那么,存储在物理内存上的数据当然是在那的。就像前面打的比喻,你的脚印仍然在雪中,除非雪化了(物理内存被回收)。
那么,为什么函数返回时,那个函数的栈帧对应的物理内存不会被回收?其实我们可以反问一下,怎么被回收?怎么去触发回收?这里你是找不到回收的依据的。
所以说,foo_a返回后,输出的*p的值是0xff。
前面我们依据汇编画了foo_a的栈结构,下面我们简要的把foo_b的反汇编和栈结构画出来:
323 080483f6 <foo_b>: 324 80483f6: 55 push %ebp 325 80483f7: 89 e5 mov %esp,%ebp 326 80483f9: 83 ec 10 sub $0x10,%esp 327 80483fc: c7 45 fc ff ff 00 00 movl $0xffff,-0x4(%ebp) 328 8048403: c9 leave 329 8048404: c3 ret
也就是说,函数foo_b执行完成以后把foo_a中a的地址处的值置成了0xffff。所以说,foo_b返回后,输出的*p的值是0xffff。
以上代码使用gcc 无优化编译连接,反汇编使用objdump -D。
如有错误请不吝赐教,谢谢!
相关文章推荐
- linux网络编程5:gethostbyname&&向固定地址发起请求得到返回页面字符
- <linux><ubuntu>基础工作
- (九)洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT
- android开发学习---linux下开发环境的搭建&& android基础知识介绍
- 【C++基础之三】函数中局部变量的返回
- Unix & Linux基础笔记(3)
- (十)洞悉linux下的Netfilter&iptables:网络地址转换原理之SNAT
- Unix & Linux基础笔记(4)
- C语言函数不能返回局部变量的地址
- 【基础】linux系统-->关机
- (九)洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT
- 如果从函数中返回局部变量的地址,引用或者指针的形式,则变量类型必须是静态的或者常量,即不在栈中存储
- [Linux&SVN] Linux下SVN基础操作流程
- (九)洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT
- Linux 基础: 挂载镜像文件(Mount & ISO)
- 返回局部变量的地址
- Linux网络编程基础---IPV4地址
- (十)洞悉linux下的Netfilter&iptables:网络地址转换原理之SNAT
- Linux基础——chown&chgrp 更改用户及属组
- Linux设置环境变量小结:设置永久变量&临时变量 全局变量&局部变量