深入汇编语言来理解C语言中的传值和传址调用

原创 2010年01月21日 00:16:00

     这个问题是由我上次和一个同学讨论引起的,这个问题在我的另一篇博客 中有写到:大概如下,当一个新参为指针的函数接受一个指针作为实参时,但是这个实参指针没有初始化,然后我在这个函数中用malloc为其分配内存,那么退出这个函数之后,这个实参是什么呢?
    下面,我写了这个文件来测试test_vp.c

------------------------------------------test_vp.c-----------------------------------------

#include<stdio.h>
char a,b;
char *pa,*pb;
char **pp;
void func_v(char x){                                                 /*(一)*/
    printf("in func_v x=%c/n",x='X');
}
void func_vp(char *px){                                           /*(二)*/
    printf("in func_vp *px=%c/n",*px='C');
}
void func_vp1(char *px){                                         /*(三)*/
    px=&b;
    printf("in func_vp1 *px=%c/n",*px);
}
void func_pp(char **pp){                                         /*(四)*/
    printf("in func_pp **pp=%c/n",**pp='I');
}
void func_pp2(char **pp){                                       /*(五)*/
    pp=&pa;
    printf("in func_pp2 **pp=%c/n",**pp);
}
void func_pp3(char **pp){                                       /*(六)*/
    *pp=pa;
    printf("in func_pp3 **pp=%c/n",**pp);
}

int main(){
    a='A';
    b='B';
    pa=&a;
    pb=&b;
    pp=&pb;

    printf("before func_v a=%c/n",a);
    func_v(a);                                                                  /*(1)*/
    printf("after func_v a=%c/n/n",a);

    printf("before func_vp *pa=%c/n",*pa);
    func_vp(pa);                                                             /*(2)*/
    printf("after func_vp *pa=%c/n/n",*pa);

    printf("before func_vp *pa=%c/n",*pa);
    func_vp1(pa);                                                           /*(3)*/
    printf("after func_vp1 *pa=%c/n/n",*pa);

    printf("before func_pp **pp=%c/n",**pp);
    func_pp(pp);                                                             /*(4)*/
    printf("after func_pp **pp=%c/n/n",**pp);

    printf("before func_pp2 **pp=%c/n",**pp);
    func_pp2(pp);                                                           /*(5)*/
    printf("after func_pp2 **pp=%c/n/n",**pp);

    printf("before func_pp3 **pp=%c/n",**pp);
    func_pp3(pp);                                                           /*(6)*/
    printf("after func_pp3 **pp=%c/n/n",**pp);

    return 0;
}

--------------------------------------------------------------------------------------

    分析上面程序的输出,可以很轻易的判断调用(1)是普通传值调用,虽然在函数(一)中修改了参数的值,

单数调用前后a的值不会改变;调用(2)是常见的传址调用,且在函数(二)中修改了指针指向数据的值,这

时,指针取消引用后的值一同改变;

    关键在于:调用(3)也是常见的传址调用,但是在函数(三)中改变的不是指针指向数据的值,而是指针,

那么调用(3)以后,指针指向数据的值会改变吗?先看看调用(4)调用了函数(四),虽然是二级指针,但是‘

在函数(四)中修改的是指针指向的最终数据的值,所以和函数(二)没有本质区别;调用(5)调用函数(五),

在函数(五)中修改了指针,那么调用以后,取消指针引用后的值也就是指针指向的数据的值会改变吗?参考调用(3)

就可以知道,不会改变!调用(6)还是传递二级指针,且在函数(六)中修改的是指针取消一次引用,那么调用后

指针指向的数据值改变吗?会!

       分析:上面的六个函数产生的效果还真不怎么好解释清楚,首先,要明白函数传递的参数到底是什么?例如:

函数(一)的参数是char x,函数(二)的参数是char *px,这个当然好理解,函数(一)传递的参数当然是x,函数

(二)传递的是px,由于写法上的视觉错误,很容易将函数(二)的参数认为是*px,记住,指针符号'*'是修饰变量的,

它不是变量的一部分,所以,要理解函数的传递,我们把char *设为我们假想的一种类型,如my_char,也就等价于

typedef char *mychar;这样函数(二)就变成了

void func_p(my_char px){...}

我觉得理解这个函数就很容易了,如果你在函数中修改了px,那么是不是相当于你在函数(一)中修改了x呢?一模一样!

所以上面的函数调用(3),在函数(三)中修改了指针px,这根本不会影响到全局变量,因为它修改的是局部变量!

       以此类推,函数调用(4)(5)(6)的结果也就好理解了,虽然它们都是传址调用,但只有在函数(五)中修改了指针

所以调用(5)不会影响全局变量。所以,很久很久以前,书上特别强调的什么要想修改传递的实参,那么就用传址调用,

这根本就是扯淡,敷衍了事。如果还没有明白,那么,我使用下一个更容易对比的例子,然后我们看看汇编代码

就知道怎么回事了。

-------------------------------------------test_vp2.c-------------------------------------

#include<stdio.h>
char a,b;
char *pa,*pb;
void func_1(char *x){
    x=&b;
    printf("in func_1 *pa=%c/n",*x);
}
void func_2(char *x){
    *x='C';
    printf("in func_2 *pb=%c/n",*x);
}
int main(){
    a='A';
    b='B';
    pa=&a;
    pb=&b;
    printf("before invoke func_1 *pa=%c/n",*pa);
    func_1(pa);
    printf("after invoke func_1 *pa=%c/n/n",*pa);

    printf("before invoke func_2 *pb=%c/n",*pb);
    func_2(pb);
    printf("after invoke func_2 *pb=%c/n",*pb);
    return 0;
}

------------------------------------------------------------------------------------

      编译运行程序,可以看到调用func_1函数前后,*pa没有改变;而调用函数func_2之后,*pb改变了。下面来查看汇编代码

1)编译成汇编

      $gcc -S test_vp.c

      由于生成的汇编代码有109行,所以这里不贴出代码,贴出关键的几个地方,就可以发现两个函数的区别,为什么一个

修改的是局部变量,而另一个是全局变量;注意:局部变量都存储在栈中,当函数调用结束后,自动失效,而全部变量根据

是否初始化分别存储在数据段和bss段中。

 -----------------------------------------------test_vp2.s--------------------------------------

............

func_1:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $b, 8(%ebp)                   #将b的地址赋值给栈中的变量,这个变量是函数调用压入的,关于函数调用栈的细节将在另一篇博客中分析

.............

 

.............

func_2:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    8(%ebp), %eax            #将基址偏移8中的值存储到寄存器EAX中,由函数调用可知这是个指针
    movb    $67, (%eax)                 #将立即数67,即'C'赋值给EAX中存放的值表示的内存单元中。

..............

.............

main:

............

 

    movl    pb, %eax
    movl    %eax, (%esp)
    call    func_2

.............

       上面的程序都是在gcc4.4.1下编译的,如果要单独使用gas和ld来编译链接test_vp2.s成可执行文件,那么需要修改上面的汇编文件

1)替换main为_start

2)替换main函数的返回语句

leave

ret

pushl $0

call     exit

然后使用如下命令来编译链接

$as -o test_vp2.o test_vp2.s

$ld -dynamic-linker /lib/ld-linux.so.2 -o test_vp2 test_vp2.o -lc

C语言中交换两个整数的值之传值调用和传址调用

在C语言中,一说到交换两个整数的值,大家第一反应可能是这样的代码。定义一个第三方变量来辅助交换。 #include int main() { int num1 = 10; int num2 = ...
  • JenaeLi
  • JenaeLi
  • 2016年10月02日 21:31
  • 1739

C语言教学--函数之间传值和传址的区别

函数之间参数的传递, 对于一般的概念(函数的定义,函数返回值,函数的调用等)就不在这里重述了, 对于初学者总是不好理解, 其实这和我们中学学习的f(x,y)=x2+y2-1是一样的, 我们可以把x,和...

深入讨论传值和传址

在C和C++中我们一直使用两种比较传统的形参传递方式,一种是传值一种是传址,对于他们,我们的理解是:传值调用的话,传的是内容,形参的改变不会影响实参,传址的话形参的改变会影响实参。究其原因的话:传址是...
  • a199228
  • a199228
  • 2011年07月25日 13:48
  • 518

关于传值调用和传址调用的说明

刚学习C语言的时候就很

自己对传值和传址的理解

首先,值传递只是将变量的内容复制一份而已,函数进行操作的其实是另一个变量,只是另一个变量的值和传递的变量值是相同的。 而地址传递是直接把变量的地址传递给函数,这时函数是直接对原来的变量进行操作的。所...

java学习02-传值调用和传址调用

传值调用 传值调用不会改变变量的值,在函数弹栈后,原变量的值不变。 class Demo {       public static void main(String[] args)  ...

Java中的传值引用和传址引用

传值引用主要是针对基本数据类型而言。所谓传值引用,就是在进行变量的传递过程中,传递的是变量的实际的值,是一个新的拷贝,一个变量值不会影响另一个变量值得改变。       eg: public cl...
  • MYBOYER
  • MYBOYER
  • 2013年02月19日 16:20
  • 488

Python的传值和传址与copy和deepcopy

Python的传值和传址与copy和deepcopy 1.传值和传址 传值就是传入一个参数的值,传址就是传入一个参数的地址,也就是内存的地址(相当于指针)。他们的区别是如果函数里面...
  • WHACKW
  • WHACKW
  • 2015年01月10日 18:00
  • 515

js- 引用和复制(传值和传址)

好像一般很少人讲到js中的引用和复制,不过弄清楚这个概念可以帮助理解很多东西 先讲一下很基础的东西,看看js中几种数据类型分别传的什么 引用:对象、数组、函数 复制:数字、布尔 字符串单独说明...

传值和传址易错点

例: //建立排序二叉树,每次输入一个数字就将其插入到树中,当输入完数字之后一棵排好序的//二叉树就已经创建完毕。 typedef struct BTnode { int vaule; st...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入汇编语言来理解C语言中的传值和传址调用
举报原因:
原因补充:

(最多只允许输入30个字)