麒麟子

~~

导航

<2024年10月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

统计

常用链接

留言簿(12)

随笔分类

随笔档案

Friends

WebSites

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜

[原]深入讲解函数中分配内存问题

声明:这随笔是无聊或是一时兴起写的。 没有其它什么目的。愿意看的就看。如果只是想用用C/C++不出错。能跑跑程序就行。那大可不必这么累地看下去。本博客中还有一个“基于C++规则”来解释这个东西的随笔。

我只想说,萝卜青菜,各有所爱!
我也不是一个死抠语言的爱好者。
甚至我是基本无视语言的。


写这个,纯属娱乐。。
谢谢各位捧场!!!

好了,有闲功夫的朋友们,或者说想了解“规则”之下的东西的朋友们。请继续往下看!

先看这样的代码

 1void MyNew(int *p)
 2{
 3    p = new int;
 4}

 5
 6int main()
 7{
 8    int *= 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中
}

//下面是清栈操作
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其实不是main中的那个p了。所以,最后,main函数中,p所指向的地址并没有改变。



void MyNew(int *&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}的方式,其实这个传递引用是完全等效的。甚至,反汇编后,他们是同样的代码。


终于写完了。有很多地方觉得还是没讲清楚,希望各位大大指教,小弟立马修改。 洗过头,上班去!!









posted on 2010-05-05 09:02 麒麟子 阅读(2283) 评论(13)  编辑 收藏 引用 所属分类: Programming

评论

# re: [原]深入讲解函数中分配内存问题 2010-05-05 09:39 Kevin Lynx

其实没必要考虑到这么复杂,关键在于要认清指针在这里表现出来的语法特性:
指针同普通变量一样,在函数内部的指针定义就是一个简单的局部变量:
main()
{
int *p ; // 可以简单理解为一个类型为int*的变量
}
C语言里的函数调用按值传递,所以:
void func( int *p );
func( p ); // 把变量p的值复制过去,跟p本身没有关系

void func( int *p )
{
}

函数参数依然被存储于函数局部栈里,何况,这里的实参p,按照我这里说的语法规则,只不过是main里p的拷贝,这就如同:
main()
{
int p ; // p是一个int类型的变量
func( p ); // 复制p的值给func的实参
}

func( int p )
{
}

当然,一睹汇编代码,也确实能认清真相。不过我觉得,既要从本质去看,也要从规则去看。这里的规则我主要指的是语言带给我们的整体语法感觉。恩,这个说不清了。
  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 10:05 小时候可靓了

@Kevin Lynx
另外一篇,就是按你这思路说的,不信你点进去看!!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 10:06 zuhd

mov eax,dword ptr [p]
不同与
lea eax,[p]
一个是传值,一个是传址  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 10:40 lymons

我觉得只要理解指针的概念就明白这是怎么回事儿了。
指针变量的内容,和指针指向的内容是两码事儿。
指针本身也是一个变量,它的内容存放的是它所指向空间的地址。
想要让指针指向别的地方,就得把这个指针变量的内容赋值上
这个地方的地址即可。

函数在进栈的时候,实参变量的内容 会被原样复制到函数栈上。
也就是说,指针传到函数里是它指向内容的首地址,其他变量传到函数里是它的值。在函数里改变参数的内容,只会影响栈上的变量的内容, 不会影响调用者传进来的实参。
所以,在函数里改变指针指向空间的内容,是没有问题的,因为你知道这个空间的地址。
要是改变指针指向的地址,也就是让他指向别的地方,则只会改变栈上的那个指针所指向的地址,也就是改变的是存放在栈上的地址,而非调用者传进来的实参。
因此,想要改变调用者的指针指向的地址,就得把调用者的指针的地址(指针本身也是一个变量,它也有地址)传到函数里去。

要么把形参写成二级指针(**),要么写成指针地址(*&). 后者的方式C不支持。  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 11:41 小时候可靓了

不知楼上的兄弟,有几位是看完了的呢!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 11:42 小时候可靓了

@zuhd
嗯,是的,主要就是这个!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题[未登录] 2010-05-05 13:02

这是语法都可解决的“问题”!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 13:08 空明流转

很显然,解决楼主问题的,不是语法,是语义。。。  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 13:13 小时候可靓了

@空明流转
是的,如果只在语法层次看一个语言,那还真是码农!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 13:56 空明流转

奶们都太迷信Syntax了。实际上,指针的含义实在Semantic中才确定了的。  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 14:07 小时候可靓了

@空明流转
不然,各种教材中,怎么会有初,中,高级教程呢。 讲语法的最吃香。就是第一种!  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 23:14 hook

连看了两篇关于pointer的文章,给俺的感觉就是头重脚轻,
感觉lz要进入了钻牛角尖的误区.所以不得不说一下.

一个简单的指针问题,别解释的这么复杂,反而会引起误解.

对于函数参数,就是简单应用规则而已:
传值就是传值,传地址就是传地址.
传值不能改变改变实参的内容,传地址可以改变实参的内容.
想要改变实参的内容,就得把实参的地址传入函数,
想要改变指针的地址,参照上面的规则.
另外,函数参数入栈的时候,编译器注定了要把实参拷贝到内存栈里.

解释这东西,根本都不需要什么事实依据,因为规则就是这么定的.  回复  更多评论   

# re: [原]深入讲解函数中分配内存问题 2010-05-05 23:24 小时候可靓了

大哥们,我写了两篇,如果你们怕麻烦,就去看另篇。
这个仅供娱乐!!!

还有。记住规则是可以的。。。 但你记住的,永远是规则!  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理