C语言的实参与形参

时间:2022-08-29 20:18:39

  在C语言中调用函数交换两个数的数值是一个经典的问题。

 #include<stdio.h>
void swap(int x,int y);
void main()
{
 int a=3,b=4;
 swap(a,b);
 printf("a=%d,b=%d\n",a,b);
}
void swap(int x,int y)
{
 int t;
 t=x;x=y;y=t;
}

执行的结果,发现值是没有交换的。在这种情况下传给形参的是变量的值。传递是单向的,即如果在执行函数期间形参的值发生变化,并不传回给实参,这就是值传递方式。因为在调用函数期间,形参和实参不是同一个存储单元。在主函数中a ,b 对应的存储单元的值并没有发生什么变化。

下面,我将上面的代码编译为汇编代码(gcc -S swap.c)。

swap:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)  //将edi寄存器中的值(3)移入栈空间
movl %esi, -24(%rbp)  //将edi寄存器中的值(4)移入栈空间
movl -20(%rbp), %eax
movl %eax, -4(%rbp)
movl -24(%rbp), %eax
movl %eax, -20(%rbp)
movl -4(%rbp), %eax
movl %eax, -24(%rbp)//实现了栈空间内的数值交换
popq %rbp
ret
main:
pushq %rbp
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $3, -8(%rbp)//main函数中的栈空间,数值为3

movl$4, -4(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call swap
movl -4(%rbp), %edx
movl -8(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
leave
.cfi_def_cfa 7, 8
ret

根据上面的注释可以知道,swap只是实现了栈空间内的数值交换,而这个交换的过程同main函数中数值3,4占用的内存是没有关系的,也就说函数为形参变量x,y分配了临时的栈空间。


但是如果改用指针,这个问题就可以解决。

#include<stdio.h>
void swap(int *x,int *y);
void main()
{
 int a=3,b=4;
 swap(&a,&b);
 printf("a=%d,b=%d\n",a,b);
}
void swap(int *x,int *y)
{
 int t;
 t=*x;*x=*y;*y=t;
}

这是因为,使用指针,传递的是a ,b的地址,调用函数,从而改变了地址a,b中的数值。

同样,我将上述代码编译为汇编:

swap:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -24(%rbp)//将rdi中的值(指针,为数值3占用内存的地址)赋值给-24(%rbp)指向的内存中
movq %rsi, -32(%rbp)//将rsi中的值(指针,为数值4占用内存的地址)赋值给-32(%rbp)指向的内存中
movq -24(%rbp), %rax//为数值3占用内存的地址赋值给rax
movl (%rax), %eax//eax中存储的是数值3
movl %eax, -4(%rbp)
movq -32(%rbp), %rax
movl (%rax), %edx//edx中存储的是数值4
movq -24(%rbp), %rax
movl %edx, (%rax)//将数值4赋值带给原有3占有的内存中
movq -32(%rbp), %rax
movl -4(%rbp), %edx
movl %edx, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
main:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $3, -8(%rbp)
movl $4, -4(%rbp)
leaq -4(%rbp), %rdx//rdx中存储的是栈空间的地址,指针指向的内存空间存储的是4
leaq -8(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call swap
movl -4(%rbp), %edx
movl -8(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
leave
.cfi_def_cfa 7, 8
ret

如果想要改变指针值,传递指针参数,这样也是出现问题,如下面的例子:

#include <stdio.h>


void fun(int *ptr)
{
   int i;
   int a[10];
    for(i=0;i<10;i++)
    {
        a[i]=i;
    }
    ptr=a;
}
int main()
{
int *b=NULL;
fun(b);
printf("%d\n",b[4]);
}

例子中传递的是指针b,在函数fun()中,将数组a的地址赋给b,在返回后同样没有改变指针b的值(b中存储的是地址,是一个指针)。在这种情况下,如果采用二重指针,就能够改变指针b的内容,是b指向数组a的起始地址。修改后的程序如下:

#include <stdio.h>
void fun(int **ptr)
{
   int i;
   int a[10];
    for(i=0;i<10;i++)
    {
        a[i]=i;
    }
    *ptr=a;
}
int main()
{
int *b=NULL;
fun(&b);
printf("%d\n",b[4]);
}

这是因为,函数中传递的是指针b的地址,通过*ptr将b中的内容赋值为数组a的地址,从而实现目标。

可以总结为一句话,在函数参数传递中,如果要改变实参的值,就必须传递实参的地址。比如,实参是一个数值,就要传递数值的地址,被调用函数中的形参就是一维指针;如果实参是一维指针,被调用函数中的形参就需要是二重指针。牢记,调用函数期间,形参和实参不是同一个存储单元。