【答疑解惑】C/C++参数传递

有群友问如下一个问题,他说在下图中sun函数内部的打印是对的,但是为什么调用结束之后主调的结果确是错误的。也就是说,函数sun为什么不能把相加的结果带回主调函数?

要正确理解这个问题,就要了解C/C++语言的参数传递,以及变量在内存中的存储这两个问题,这两个问题其实也是初学者在C/C++学习很容易出现的问题。

C/C++的参数传递

C/C++规定参数传递用于都是传递数值,而不会把参数的地址传递给子函数。注意不要把指针作为参数混为一谈,以为指针就是传递了参数的地址!本质上指针也是传递的一个数值,初学者一定要理解这点。比如以下两个代码片段:

int fun(int n, int *pn)

{

}

int main(void)

{

int a = 5;

int b;

fun(a, &b);

}

在函数fun传递a和&b时,是将a的数字(5)和b的地址这个数字传给子函数,虽然在这里第二个参数的意义是一个变量的地址没错,但是在传给fun时,它只认为他是一个数,不会因为加了&符号就把它认为是一个变量的地址。比如b的地址为0x00401234,也就是&b为0x00401234,但是在汇编层面的参数传递(可能放到寄存器中,也可能放到某个内存地址),它只管把0x00401234这个数值传给子函数,不会关心这个数本身是一个地址,还是地址的地址,甚至地址的n次方地址^_^,所以说C/C++传递的永远都是值传递。至于在子函数里面你想怎么理解这个数,那是你在编写子函数的时候决定的,所以C/C++中你可以把指针作为整数进行运算,这也是C/C++灵活的一个方面。

变量在内存中的存储

这个问题网上有大量的文章,程序员互动联盟网站(http://www.coderonline.net)以及以往的文章中也出现过,这个只是强调一下在子函数中申请的变量(包括形参变量)都是在调用子函数是有栈指针ESP直接移动产生的。也就是说当需要一个栈中的变量时,栈指针就一定一个位置,于是留下一个32位的内存空间就作为这个变量的存储空间。这个过程在所有的函数中都一样,也包括主函数。这里需要强调的是每个函数在调用时都有一个栈帧(基地址就是BSP寄存器决定)。所以实际上每一个函数内部的变量内存单元都可能跟其他不一样,因为他们的栈帧都很可能不同,位置也就不同了。至于什么静态变量、全局变量等等就不在这里讨论,因为跟今天的这个问题没有什么关系。

OK,现在两个概念都基本说清楚了,我们来分一下这个问题是怎么出现的。

首先,主函数分配了三个变量c、d、f (a,b,sum作为指针变量在这里意义不大,可以直接传变量的地址),并在调用子函数时把三个变量c、d、f的地址作为数值传递给sun这个函数。根据上面的分析,c、d、f这三个变量的内存单元是在主函数main这个栈帧里面存在。在子函数处理时,定义了一个变量s,这个s在子函数的栈帧中,用于接收形参a和b这个数值分别作为指针所指向的内存单元的值的和。到这里为止,一切都很正常。但是接下来的一句sum = &s这个语句把一切都搞砸了。我们看看sum是主函数传递过来的f的地址这个数值,假设这个数值是0x00401234,但是这里我们看到它把sum重新改写为s的地址,假设为0x00405678,原本sum的数值是一个位于主函数栈帧中的存储单元f的地址,也是希望向这个地址中0x00401234写入结果,自然就能再主函数中访问的,但是现在换成了一个子函数sun栈帧中的一个变量的地址0x00405678。所以在子函数中打印这个sum所指向的地址的值(s)是没有问题的,但子函数已把f的地址偷梁换柱了,执行完后,主函数的f的地址所在的单元0x00401234根本没有被赋过值,所以主函数的f原来是什么还是什么。

理解之后,要正确解决这个问题就是不要覆盖sum这个数,并且把加的结果放到sum这个里面即可,比如*sum = s。

总结起来就是,本来要用sum这个包接收物品,但是子函数却另外拿了一个包替换了这个包,并且往新的包里放了,原来那个包的所有者把包收回去后,当然在包里面没有想要的物品。但是要理解整个过程,需要理解上面的两个方面的C/C++知识。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-09-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏匠心独运的博客

消息中间件—RocketMQ消息存储(二)一、RocketMQ存储整体设计架构回顾二、RocketMQ存储关键技术—再谈Mmap与PageCache三、RocketMQ存储优化技术四、RocketMQ

文章摘要:上篇中主要介绍了RocketMQ存储部分的整体架构设计,本篇将深入分析RocketMQ存储部分的细节内容 在本篇文章中,小编将继续深入分析与介绍Ro...

932
来自专栏一个会写诗的程序员的博客

《Kotin 极简教程》第9章 轻量级线程:协程(2)《Kotlin极简教程》正式上架:

如果需要依次调用它们, 我们只需要使用正常的顺序调用, 因为协同中的代码 (就像在常规代码中一样) 是默认的顺序执行。下面的示例通过测量执行两个挂起函数所需的总...

822
来自专栏Java架构沉思录

如何优雅地用Redis实现分布式锁

在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同...

38817
来自专栏C/C++基础

C/C++ sizeof(下)

sizeof作用于基本数据类型,在特定的平台和特定的编译器中,结果是确定的,如果使用sizeof计算构造类型:结构体、联合体和类的大小时,情况稍微复杂一些。

742
来自专栏C#

C#创建安全的栈(Stack)存储结构

    在C#中,用于存储的结构较多,如:DataTable,DataSet,List,Dictionary,Stack等结构,各种结构采用的存储的方式存在...

1886
来自专栏Java Edge

MySQL的锁1 MySql的三种锁2 表锁的锁模式3 MyISAM的并发锁4 InnoDB锁问题5 关于死锁6 总结7 索引与锁

5206
来自专栏Java3y

多线程三分钟就可以入个门了!

22111
来自专栏IT技术精选文摘

Kafka剖析系列之Consumer解析

High Level Consumer 很多时候,客户程序只是希望从Kafka读取数据,不太关心消息offset的处理。同时也希望提供一些语义,例如同一条消息只...

2056
来自专栏编程之旅

线性表的顺序存储结构

举个简单的例子,蔺老师在给九班学生安排座位之前,会让学生们从矮到高按照身高的高矮升序排列,假如蔺老师的班上只有十个学生,而全班共有50个座位,那蔺老师会把这10...

782
来自专栏青玉伏案

JVM的内存区域划分以及垃圾回收机制详解

在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉。因为JVM中有垃圾自动回收机制。在之前的博客中我们聊过Objec...

1827

扫码关注云+社区