关于C语言中一,二级指针函数中的使用和c语言中和函数发生调用时,实参和形参都会占用内存吗?

最近在讨论着两个看上去学c的人都知道的东西,不就是指针嘛,比变量高级一点而已。还有实参和形参,不就是函数使用时的那些东东嘛。

话说后者在csdn发了个帖子,大家讨论了一下,发现很多人C语言的基础很扎实,而且汇编的功底也很强。前者的话只是我个人的学习总结而已。现在先来讨论讨论后者吧。

我结合一下帖子。

1. 函数发生调用时,实参和形参都会占用内存吗???

 

#include <stdio.h>
 int fun(int a);
 void main()
 {
 int a=1;
 fun(a);
 printf("%d\n",fun(1));

 printf("%d\n",a);
 }
 
int fun(int a)
 {
 //a=3;
 a=a+1;
 return a;
 }

贴上x86下面的汇编看看:

恩,我贴这个汇编出来看下吧:
.file "app.c"
 .section .rodata
 .LC0:
 .string "%d\n"
 .text
 .globl main
 .type main, @function
 main:
 pushl %ebp
 movl %esp, %ebp
 andl $-16, %esp
 subl $32, %esp
 movl $1, 28(%esp)
 movl 28(%esp), %eax
 movl %eax, (%esp)
 call fun
 movl $1, (%esp)
 call fun
 movl $.LC0, %edx
 movl %eax, 4(%esp)
 movl %edx, (%esp)
 call printf
 movl $.LC0, %eax
 movl 28(%esp), %edx
 movl %edx, 4(%esp)
 movl %eax, (%esp)
 call printf
 leave
 ret
 .size main, .-main
 .globl fun
 .type fun, @function
 fun:
 pushl %ebp
 movl %esp, %ebp
 addl $1, 8(%ebp)
 movl 8(%ebp), %eax
 popl %ebp
 ret
 .size fun, .-fun
 .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
 .section .note.GNU-stack,"",@progbits 


代码卡上去都很简单明了,然后然后就是各种讨论了:

先给出官方的说法,应该是书上普遍的说法吧:

函数的形参和实参具有以下特点:  
 1. 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。  
 2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。 
 3. 实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误。  
 4. 函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

比较满意的答案:

a.

我的理解,这和不同CPU架构的编译器有关
 以ARM为例,调用函数的参数都是都过ATPCS规则传递的,参数少于4个,使用r0-r3四个CPU内部通用寄存器(不在内存),多于四个就要使用栈了,这个时候才占内存
 其他架构的cpu肯定会不同 

b.

我的理解,这和不同CPU架构的编译器有关
 以ARM为例,调用函数的参数都是都过ATPCS规则传递的,参数少于4个,使用r0-r3四个CPU内部通用寄存器(不在内存),多于四个就要使用栈了,这个时候才占内存
 其他架构的cpu肯定会不同
 

c.

我理解的是在汇编里面没有实和形的概念,只是对内存的操作罢了。
 下面的代码和汇编
 #include <stdio.h>
 int fun(int a);
 void main()
 {
 int a=1;
 fun(a);
 // printf("%d\n",fun(1));
 
// printf("%d\n",a);
 }
 
int fun(int a)
 {
 //a=3;
 a=a+1;
 return a;
 }
 .file "app.c"
 .text
 .globl main
 .type main, @function
 main:
 pushl %ebp
 movl %esp, %ebp
 andl $-16, %esp
 subl $32, %esp      //分配栈空间 给局变量以及要调用的函数的形参
 movl $1, 28(%esp)   //给局部变量a赋值1 即 a = 1;
 movl 28(%esp), %eax // 指令不支持双存储器操作数(即不再ss型指令)同过register过渡,(SR型)
 movl %eax, (%esp)   //给形参赋值, 形参a的地址是%esp, 实参a的地址时%esp + 28;
                        //参数和局部变量的入栈未必要通过push。
 call fun
 leave
 ret
 .size main, .-main
 
.globl fun
 .type fun, @function
 fun:
 pushl %ebp
 movl %esp, %ebp
 addl $1, 8(%ebp)  //将形参进行+1操作。
 movl 8(%ebp), %eax
 popl %ebp
 ret
 .size fun, .-fun
 .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
 .section .note.GNU-stack,"",@progbits
 

其实汇编代码说的很清楚了。
 
X86架构下,栈的增长方向是向地址方向生长的。
 
每个函数相当于一层,在汇编中,我称之为一个frame。
 该frame 有一个基地址,就是ebp的值,还有一个栈顶指针esp。
 所以函数调用中的,前两条指令一般都是:
 AT&T的格式:
 pushl %ebp;  //保存上一层的frame的的基地址
 movl %esp, %ebp; //子函数的基地址。
 
Intel格式:
 push ebp;
 mov ebp,esp;
 
由于栈是向低地址生长的,所以子函数中
 addl $1, 8(%ebp)  //将形参进行+1操作。
 此时形参的a的地址是 ebp+8,因为call 转子函数时会将返回地址,即call指令的下一条指令的地址
 (eip+5)入栈,32位的地址,4字节; 即 eps -= 4; 还有pushl %ebp; 又有esp -=4;
 所以esp+8 就是形参a的地址。
 
对于一个子函数来说,一般 [ebp + n*4](n >= 1)是形参, [ebp-4*n](n>=1)是该子函数的局部变量。

      其实从那段简单的汇编角度来看,个人理解可以发现如帖子里面所的会根据编译器和cpu来决定,比如这个x86就简单的使用esp栈指针,ebp基指来完成内存的操作。在这边我的意愿是吧理解成这样:在调用fun之前的那个阶段,会根据你的形参从右到左依次从局部变量的内存空间copy到新的栈空间,这个空间理解为了栈帧。所以在esp继续移动之前,我们发现当下栈顶放的是几个形参的内容,这里我就理解为形参,实际也没必要去想是形参。但是这个空间可以确定不是函数运行完就释放的。这个查了一下所谓的函数调用约定有如下一句:__cdecl 参数自右向左压栈;如果有this指针,作为最后一个参数压栈,调用者清栈。而我们默认的就是这种约定,从这个角度我的理解就是这个栈空间啊,得由main来完成清栈而不是被调的fun来完成。

总结出来就是这个形参开不开内存,关键还是看自己的程序,CPU架构,编译器是否优化,函数调用的约定性等几点,最后这个形参是不是函数调用结束就回收也得看上面的几点。比较同意上面c的观点。在汇编阶段没有必要去分实参和形参了,都是对内存操作。

结贴哈哈。

 

2.另一个问题

*p和**p,这个普通的fun(*p),fun(**p)。

在函数中调用时,这几个指针的内存分配已经在函数内部对形参的操作,先给个内存的分配图

这图展示的是最简单的内存分配了,黄色区域来至于变量如果fun(*),那么就直接传入p,fun(**)就直接fun(&p),这里充分说明了函数是传值调用,最复杂的指针工作也是一个地址数值。

a.这里可以看到在fun(*)里面如果要改变p的数值那是可以的但是只是修改属于fun里面的p即黄色区域,这块可以从上面的问题中理解为给fun用的内存块,是对局部变量p的copy。所以这很好的说明了在函数返回后p还是=&a,并没有发生变化。因此这个说明了一级指针调用的函数内,对p操作对外部没有意义,使用*p来操作这个红色区域这倒可以。当然只是一种方式而已,只是结合着上面的问题引发的。就这样而已

下面来说通过2级指针修改p,fun(**p)调用传入的是黄色区域的&p那么函数内部如果给*p赋数,那么返回后p就发生了变化指向了别的地址空间。这个是2级操作的好处。

这个简单的理解的好处就是可以让你对函数内外的变量在内存中的存在与可用性进一步加深了理解。

 

学习还是得深入,多写代码,多看代码,写出自己的高质量代码。

 

 

 

 

 

 

 

 

 


 


 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值