深入理解函数中分配内存的问题
先看这样的代码
2{
3 p = new int;
4}
5
6int main()
7{
8 int *p = NULL;
9 MyNew(p);
10}
开始写了一篇函数中分配内存的问题,通过说明他们产生了拷贝,而导致p不能成功分配。但并未提出事实根据,下面我们来仔细看看具体原因。
我们需要弄清两点:1、main函数中的p与MyNew函数中的p是不是一样;2、如果不一样,是怎么导致了不一样的。
第一点很好看,我们可以在编译器(VC环境,我用的是VS 2005)的监视窗口中跟踪p的地址。
在监视窗口中增加一个对 &p的监视,然后我们在int*p = NULL处添加一个断点。单步执行,停在MyNew函数前,此时我们可以看到,&p的值为 0x0012ff60 . 然后,我们单步进入MyNew函数,此时我们可以发现,&p的值变成了0x0012fe8c 明显,它们不是同一个东西,这样在MyNew操作的时候,操作的就不是我们想要操作的那个p(0x0012ff60). 好了,不要去猜测这两个数字之间的关系,接下来会给你一个满意的答案.
先看看下面这个反汇编的结果
int *p = NULL;
0041153E mov dword ptr [p],0
MyNew(p);
00411545 mov eax,dword ptr [p]
00411548 push eax
00411549 call MyNew (41116Dh)
红色部分就是将p作为参数压栈,然后call MyNew,注意,此时我们的p已经被保存起来了。
void MyNew(int *p)
{
004114C0 push ebp
004114C1 mov ebp,esp
004114C3 sub esp,0CCh //红色:分配33*4Bytes 临时空间
004114C9 push ebx
004114CA push esi
004114CB push edi
004114CC lea edi,[ebp-0CCh]
004114D2 mov ecx,33h
004114D7 mov eax,0CCCCCCCCh
004114DC rep stos dword ptr es:[edi] //蓝色:初始化分配的空间为 0xcccccccc
p = new int;
004114DE push 4
004114E0 call operator new (411190h) //调用new 返回值存放于eax中。
004114E5 add esp,4
004114E8 mov dword ptr [ebp-0C8h],eax //将new出来的地址放到ebp-0c8h中
004114EE mov eax,dword ptr [ebp-0C8h] //将new出来的值放到eax中,作为返回值。
004114F4 mov dword ptr [p],eax //将eax中的值放入p中 这也是为什么 int* MyNew() {return new int;}能成功的原因
}
//下面是清栈操作
004114F7 pop edi
004114F8 pop esi
004114F9 pop ebx
004114FA add esp,0CCh //清除临时变量
00411500 cmp ebp,esp
00411502 call @ILT+325(__RTC_CheckEsp) (41114Ah)
00411507 mov esp,ebp
00411509 pop ebp
0041150A ret
上面的东西不能说明根本问题,因为没有作任何分析,下面我们就来仔细分析一下,特别是最后的 004114F4 mov dword ptr [p],eax 有人就会问,既然已经放回了p中,为啥p还是没变呢。
这就是new之间的堆栈空间示意图,可以看出,我们传入的参数是ebp+8,而当new回来后,却用的是004114E8 mov dword ptr [ebp-0C8h],eax 很明显,ebp-0c8 是临时分配的空间。 而放入的那个p, 的确,它是放了,但是,这个p,已经不是那个p了,这个p指向的是我们栈空间里的临时变量,当函数返回后,p自然就清除了。 所以,最后,main函数中,p所指向的地址并没有改变。
{
p = new int;
}
我们将代码稍作修改,改成传递指针的引用,那又会发生什么呢。首先,我们按照上面的方法检测其地址。 你会发现,两个函数中的地址都是 0x0012ff60.
那,为什么会这样呢,我们看看两个地方,第一就是参数传递时的压栈。
00411535 lea eax,[p]
00411538 push eax
00411539 call MyNew (4111F4h)
可以看出,这次传递的,并非是像开始一样 mov eax, dword ptr[p] 。二者的差别在于,上一次(没有采用引用传递)传递的是值,而这一次(采用了引用传递)传递的是指针p的地址。
接下来,我们再来看看刚刚new出来之后赋值的地方。
004114E8 mov dword ptr [ebp-0C8h],eax
004114EE mov eax,dword ptr [p]
004114F1 mov ecx,dword ptr [ebp-0C8h]
004114F7 mov dword ptr [eax],ecx
可以发现,这正是我们传中说的:取得p的地址,采用*p求出p所指向的地址。然后对*p赋值,以改变它的值。。
还有一种就是指针的指针void MyNew(int** p){*p = new int}的方式,其实这个传递引用是完全等效的。甚至,反汇编后,他们是同样的代码。
结论:从上面的代码中我们可以看到,如果想要在函数内改变参数的值,则只能通过传递他的地址(引用也是传地址)方式,然后对其地址指向的内容进行变更!!!
终于写完了。有很多地方觉得还是没讲清楚,希望各位大大指教,小弟立马修改。 洗过头,上班去!!
转载于:https://www.cnblogs.com/qilinzi/archive/2010/05/05/1940496.html
深入理解函数中分配内存的问题相关推荐
- 二级指针在子函数中申请内存的两种方式
二级指针:在主函数中申明变量,在子函数中分配内存,有两种方式返回二级指针.在这个过程只有深刻理解了C的函数调用模型,以及指针的内存模型,才能够掌握好. #pragma once #include &l ...
- 深入理解Java中的内存泄漏
理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...
- 关于调用子函数给主函数指针分配内存
典型的错误例子如下 在这个主函数的指针给子函数传递一个指针,而在子函数中形参有开辟了一块内存,此子函数的指针的内存里存储的地址与主函数是同一地址, 即主函数的指 针和子函数形参的指针都指向同一块内存的 ...
- C语言calloc()函数:分配内存空间并初始化
http://c.biancheng.net/cpp/html/134.html 头文件:#include <stdlib.h> calloc() 函数用来动态地分配内存空间并初始化为 0 ...
- malloc在函数内分配内存问题
malloc函数用法可参考:C语言中 malloc函数用法 及 malloc函数 代码: void fun(char * p) {p=(char *)malloc(100); }void main() ...
- 翻译 | 理解Java中的内存泄漏
猪年第一篇译文,大家多多支持! 原文自工程师baeldung博客,传送门 1. 介绍 Java 的其中一个核心特点是经由内置的垃圾回收机制(GC)下的自动化内存管理.GC 默默地处理着内存分配和释放工 ...
- web前端高级JavaScript - 一道题彻底理解函数中this指向和闭包作用域
关于函数中this指向和闭包作用域的一道练习题 javascript 代码 var x = 3,obj = {x: 5}; obj.fn = (function(){this.x *= ++x;ret ...
- Linux X64 粗糙的理解函数中的指令调用(call,leave,ret)和栈的使用过程(push pop)
#include"../common"//.intel_syntax noprefix#define as(...) asm volatile(__VA_ARGS__)/*函数的内 ...
- C# Marshal的使用,非托管内存中分配内存注意的问题
IntPtr ptr = Marshal.AllocHGlobal(704* 576 * 3); 如果没有手动释放内存,会有内存溢出: 发生OutOfMemoryException 没有足够的内存继续 ...
- Linux内核中常见内存分配函数
1. 原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分 ...
最新文章
- 页面 切换 中英文 怎么实现_【完美解决】AE CC2018表达式错误 ae cc2018如何进行中英文转换?...
- js 显示当前时间(年月日时分秒)——getYear()与getFullYear()
- Word2Vec中文语料实战
- WCF事务编程[中篇]
- vivo解bl锁_mi8 8SE 小米8解帐户锁 解ID锁 解激活锁
- PrincipleCTEbook
- Hadoop入门(二十三)Mapreduce的求数量最大程序
- java处理unicode_C# JavaScript Java 与 中文 unicode 处理
- Redis持久化机制(RDB VS AOF)
- Linux bash符号及含义,Linux下用bash命令执行名称中带有(符号的目录注意事项
- 【MySQL】MySQL 8 PROCEDURE ANALYSE命令使用
- 让C#语言充当自身脚本!——.NET中的动态编译
- matlab 向量法建数组(推荐)
- 浏览器获取CA认证流程
- Linux学习---Day03
- java 开发必备的安全架构知识
- c语言用fun函数求最大公约数,c语言求最大公约数
- 局域网稳定性测试软件,局域网速度测试
- FIL在十月份的ICO流通减产
- html5设置全屏背景图,HTML5 body设置全屏背景图片 如何让body的背景图片自适应整个屏----实战经验...
热门文章
- 将一个javaBean中非空的属性合并到另一个javaBean中
- android圆盘布局,Android绘制圆盘控件
- 科罗拉多大学波尔得分校计算机科学,科罗拉多大学波尔得分校排名
- mysql+alter+int_MySQL Alter命令
- Android广播时间——实现强制下线功能
- java中常用的类——Object类
- java方法语句错误需要标识符_java错误需要标识符_Java错误 找不到符号
- IDEA 常用设置 与 常用操作(二)
- LayaAir 位图添加遮罩与滤镜
- Linux 命令之 crontab 计划任务与自动同步系统时间