热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c语言函数变量地址符,C语言中取地址符做函数形参?——引用的讨论

取地址符&做函数形参?C语言强大且危险引入这个问题花去了整整一天的研究。先看一段严蔚敏的《数据结构》中栈的例程:这里面的&S第一遍看的时候想当然的认为是

取地址符&做函数形参?

C语言强大且危险

引入

这个问题花去了整整一天的研究。

先看一段严蔚敏的《数据结构》中栈的例程:

这里面的&S第一遍看的时候想当然的认为是取了SqStack结构体的S的地址,没有细想。然后又看到了这句。

// platform: VC++6.0

Status Pop(SqStack &S, SElemType &e); //取地址符?

1

2

我开始突然发现,这真的是取地址符吗,对照了我自己写的程序,仔细推敲发现不太对。

仔细看这里的&e,如果这是个整型的栈,那么SElemType就是int,那么这里就等于:

Status Pop(SqStack &S, int &e); //很奇怪

1

类比的疑问

我们都知道:

int a,b; /* 定义了两个整型的指针 */

int **a, *b; / 定义了整型指针的指针 */

1

2

那么难道说是

int &e; // 定义了以一个整型数为地址的变量e?

1

仔细看下接下来的函数定义:

显然这里可以看出由于top指针指向的是SElemType类型,所以e是SElemType类型的。所以以上类比显然是不对的。

C/C++中的引用参数

查找了很多的资料发现,这个实际上是C++里的形参符号,必须要在跟在数据类型的后面使用。在函数内部对形参的操作都等同于直接操作原变量。

先说形参和实参

学过C语言的都知道,一个经典的例子是关于写一个交换两个变量a,b的值的函数:

// “形参不等于实参”的经典错误示范

void swap(int a, int b)

{

int temp;

temp = a;

a = b;

b = temp;

}

void main()

{

int a = 1, b = 2;

swap(a,b)l

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

我们都知道把a,b作为形参传入时,会临时的分配形参空间读取实参a,b的值存入,这里的形参a,b实际地址是不同于原来的实参。

形象的说,实参a是一份讲义,你在调用函数的时候,函数就像学生一样去要讲义(传递的实参)。函数向系统要了张白纸(栈区空间),然后把这篇文章抄了一份拿去用了,取名也叫作a。然后他怎么修改都不会

继续准确点说, 在程序运行的时候会分配一个全局区,我们这里说的a,b实际上属于全局变量,存储在全局区,也有的地方叫做静态区。而这里的形参存储在栈区,仅仅是保存了全局量的值,所以所有对形参a,b的操作都和静态区的a,b无关。

这里实参传递给形参的过程叫做值传递。

附:C/C++程序的内存分配知识

一个由C/C++编译的程序占用的内存分为以下几个部分 :

1、栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap) ― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。这个空间是公用的,如果没有释放会使得可用堆区空间变小,最好在申请后手动释放。

3、全局区(静态区)(static)―,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放

4、文字常量区 ―常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区―存放函数体的二进制代码。

所以我们可以理解为,这里的&e是为了说明e变量不是仅仅的把值传递进了函数内部。

那怎么通过函数操作函数外部的参数呢?

根据C语言学习中标准解法,一是将实参的地址传递进函数中函数中,通过地址直接操作原变量的值;二是利用函数本身的返回。

// 利用指针的经典解法

void swap(int *a, int *b)

{

int temp;

temp = *a;

*a = *b;

*b = temp;

}

void main()

{

int a = 1, b = 2;

swap(&a,&b);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

必须先弄清楚各种地址

要理清引用参数的使用和原理,明白这个&符号和指针的区别,先必须搞清楚数据的地址。

在《操作系统》中,可以得知三种地址的区别:逻辑地址、线性地址和物理地址。

关于这三者的区别可以看这里:

操作系统逻辑地址、线性地址和物理地址

http://www.cnblogs.com/dirichlet/archive/2011/03/18/1987746.html

或者这里

http://blog.csdn.net/geekwill/article/details/52449855

这里用图简单通俗的说下,为了通俗易懂,严格上并不准确:

我们的程序在操作系统中运行的时候,会给我们的程序(进程)在内存中分配一些空间。为了方便说明,这里假设内存是16位地址(实际上32位地址支持4G内存),我们可以看到a的物理地址是0x23。

然后0x2300是什么呢,这个是进程数据段的首地址,一般我们习惯叫做程序运行的入口地址。

像上面的图所示,我们通过&a把a的逻辑地址传递进了函数swap中,然后swap函数通过*a找到a的物理地址,这个是操作系统完成的,其中会经过一些过程,需要先变换为线性地址。

那么我们可以总结:

实际上在C语言中,使用&取地址符取出的是变量的[逻辑地址],就算用汇编进行操作也是一样。变量的物理地址只有操作系统知道,实际上逻辑地址和物理地址都是32位整数(32位机)。两个不同进程,就算逻辑地址一样,实际的物理地址也不同。

这里关于各种变量的内存地址相关可以参考:

C语言内存地址基础

http://blog.jobbole.com/44845/

关于C语言的函数调用过程更加深度严谨(也更难懂)的知识,墙裂推荐这篇文章:

深入理解C语言的函数调用过程

http://blog.chinaunix.net/uid-23069658-id-3981406.html

通过引用传递和通过指针传递?

之前的两个例子,分别用常规的值传递和指针的传递实现数据交换的过程看起来不同,其实都是差不多的。实质上都是值传递。

第一个例子的执行过程:

第二个例子的执行过程:

可以看出实际上利用指针的方法也只是把a,b的逻辑地址作为一个整数通过值传递到形参里存储起来了,值传递的内容是a,b的逻辑地址。这两种方式都需要额外的开辟栈区的内存,而且指针操作是易错且不安全的。

下面是通过引用参数完成的交换过程。

// 引用参数实现交换

void swap(int &a, int &b){

int temp;

temp = a;

a = b;

b = temp;

}

// Using main to test

void main(){

void swap(int&, int&);

int a = 1, b = 2;

swap(a,b);

printf("%d %d\n",a,b);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

有些文章说道,通过引用的方式传递给函数的是变量的地址,这种方式叫做地址传递方式,还提到这是和“值传递”十分不同的方式。

有些书说道:“引用实际上是取了个‘别名’”

还有的书和文章说道引用是比通过指针传递更加高效的方式,因为不需要开辟新的内存空间用来拷贝实参的地址。

真的吗?

引用实现原理的讨论

先讨论引用实现的系列文章,大佬们讲得比较透彻,而且论据丰富。

c++中的引用的使用原理以及使用实例

http://blog.csdn.net/ztz0223/article/details/1639305

C++ 引用 参数传递 机制【强烈推荐】

http://blog.csdn.net/huqinweI987/article/details/50769096

C++引用的本质与修改引用的方法

http://blog.csdn.net/huqinweI987/article/details/24874403

举例剖析C++中引用的本质及引用作函数参数的使用

http://www.jb51.net/article/80911.htm

如果不想看干货长文的就看下下面的通俗简短讨论吧。

我们看下下面这段小程序:

int i = 0;

int &j = i; // 设j为i的一个引用

j = 10; // 将j试着改变为10

int *p = &j; // 观察引用的取地址操作

1

2

3

4

汇编(伪汇编)解析如下:

;int i = 0;

mov dword ptr [i],0; // i的内容置为0;

;int &j = i;

lea eax, [i]; // 将i的地址放入eax寄存器

mov dword ptr[j],eax; // 将i的地址传入j的内容

; j = 10;

mov eax, dword ptr[j]; // 取j的内容(i的地址)放入eax

mov dword ptr [eax], 0Ah; // 将eax地址指向修改为10;

;int *p = &j;

mov eax,dword ptr [j] // 将j的内容传给eax

mov dword ptr [p],eax // 把eax内容传入p的内容

1

2

3

4

5

6

7

8

9

10

11

12

13

14

实际上,通过对汇编的分析可以看出:

“引用是一个别名”的说法并不准确,实际上实现过程中引用也可以看成是一种指针,实际上引用变量存储的就是引用对象的地址,也要占用内存空间(和指针占用大小不同),只不过C++的标准规定了引用初始化完毕之后,对引用的操作就等于是对实际对象的操作。

虽然引用可以看做特殊的指针,对引用的操作会被编译器解释成对地址指向的目标的操作。但和*p这种取指针指向对象的方式不同,这种方式不会开辟临时空间存储指针指向的对象。如果指向对象很大,操作重复数很多,这个差异就会对性能有十分大的影响。

引用的本身值,即引用对象的地址不可以像指针变量一样修改,对引用的操作只会解释成对引用对象的操作,可以理解引用变量是一个静态的指针。

对第2条的解释,关于指针操作拷贝副本和引用节省空间的详细解释可以看上面的文章—— C++ 引用 参数传递 机制【强烈推荐】。



推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • IT方面的论坛太多了,有综合,有专业,有行业,在各个论坛里混了几年,体会颇深,以前是论坛哪里人多 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了一种求解最小权匹配问题的方法,使用了拆点和KM算法。通过将机器拆成多个点,表示加工的顺序,然后使用KM算法求解最小权匹配,得到最优解。文章给出了具体的代码实现,并提供了一篇题解作为参考。 ... [详细]
  • 正则表达式及其范例
    为什么80%的码农都做不了架构师?一、前言部分控制台输入的字符串,编译成java字符串之后才送进内存,比如控制台打\, ... [详细]
  • Jmeter对RabbitMQ压力测试
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Jmeter对RabbitMQ压力测试相关的知识,希望对你有一定的参考价值。Jm ... [详细]
  • 本文详细介绍了使用 SQL Load 和 Excel 的 Concatenate 功能将数据导入 ORACLE 数据库的方法和步骤,同时介绍了使用 PL/SQL tools 将数据导入临时表的方法。此外,还提供了一个转链接,可参考更多相关内容。摘要共计XXX字。 ... [详细]
  • 抽空写了一个ICON图标的转换程序
    抽空写了一个ICON图标的转换程序,支持png\jpe\bmp格式到ico的转换。具体的程序就在下面,如果看的人多,过两天再把思路写一下。 ... [详细]
author-avatar
小樣兒靠邊詀_853
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有