关闭

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

标签: 语言汇编c存储gcc测试
2672人阅读 评论(9) 收藏 举报
分类:

     这个问题是由我上次和一个同学讨论引起的,这个问题在我的另一篇博客 中有写到:大概如下,当一个新参为指针的函数接受一个指针作为实参时,但是这个实参指针没有初始化,然后我在这个函数中用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

0
0

猜你在找
深度学习基础与TensorFlow实践
【在线峰会】前端开发重点难点技术剖析与创新实践
【在线峰会】一天掌握物联网全栈开发之道
【在线峰会】如何高质高效的进行Android技术开发
机器学习40天精英计划
Python数据挖掘与分析速成班
微信小程序开发实战
JFinal极速开发企业实战
备战2017软考 系统集成项目管理工程师 学习套餐
Python大型网络爬虫项目开发实战(全套)
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:529891次
    • 积分:6884
    • 等级:
    • 排名:第3118名
    • 原创:141篇
    • 转载:1篇
    • 译文:0篇
    • 评论:104条
    最新评论