2025年4月10日 星期四 乙巳(蛇)年 正月十一 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > VC/VC++

引用不能绑定到临时数据

时间:01-05来源:作者:点击数:760

我们知道,指针就是数据或代码在内存中的地址,指针变量指向的就是内存中的数据或代码。这里有一个关键词需要强调,就是内存,指针只能指向内存,不能指向寄存器或者硬盘,因为寄存器和硬盘没法寻址。

其实 C++ 代码中的大部分内容都是放在内存中的,例如定义的变量、创建的对象、字符串常量、函数形参、函数体本身、newmalloc()分配的内存等,这些内容都可以用&来获取地址,进而用指针指向它们。除此之外,还有一些我们平时不太留意的临时数据,例如表达式的结果、函数的返回值等,它们可能会放在内存中,也可能会放在寄存器中。一旦它们被放到了寄存器中,就没法用&获取它们的地址了,也就没法用指针指向它们了。

下面的代码演示了表达式所产生的临时结果:

  • int n = 100, m = 200;
  • int *p1 = &(m + n); //m + n 的结果为 300
  • int *p2 = &(n + 100); //n + 100 的结果为 200
  • bool *p4 = &(m < n); //m < n 的结果为 false

这些表达式的结果都会被放到寄存器中,尝试用&获取它们的地址都是错误的。

下面的代码演示了函数返回值所产生的临时结果:

  • int func(){
  • int n = 100;
  • return n;
  • }
  • int *p = &(func());

func() 的返回值 100 也会被放到寄存器中,也没法用&获取它的地址。

什么样的临时数据会放到寄存器中

寄存器离 CPU 近,并且速度比内存快,将临时数据放到寄存器是为了加快程序运行。但是寄存器的数量是非常有限的,容纳不下较大的数据,所以只能将较小的临时数据放在寄存器中。int、double、bool、char 等基本类型的数据往往不超过 8 个字节,用一两个寄存器就能存储,所以这些类型的临时数据通常会放到寄存器中;而对象、结构体变量是自定义类型的数据,大小不可预测,所以这些类型的临时数据通常会放到内存中。

下面的代码是正确的,它证明了结构体类型的临时数据会被放到内存中:

  • #include <iostream>
  • using namespace std;
  • typedef struct{
  • int a;
  • int b;
  • } S;
  • //这里用到了一点新知识,叫做运算符重载,我们会在《运算符重载》一章中详细讲解
  • S operator+(const S &A, const S &B){
  • S C;
  • C.a = A.a + B.a;
  • C.b = A.b + B.b;
  • return C;
  • }
  • S func(){
  • S a;
  • a.a = 100;
  • a.b = 200;
  • return a;
  • }
  • int main(){
  • S s1 = {23, 45};
  • S s2 = {90, 75};
  • S *p1 = &(s1 + s2);
  • S *p2 = &(func());
  • cout<<p1<<", "<<p2<<endl;
  • return 0;
  • }

运行结果:0x28ff28, 0x28ff18

第10行代码用到了运算符重载,我们将在《C++运算符重载》一章中详细讲解。

关于常量表达式

诸如 100、200+34、34.5*23、3+7/3 等不包含变量的表达式称为常量表达式(Constant expression)

常量表达式由于不包含变量,没有不稳定因素,所以在编译阶段就能求值。编译器不会分配单独的内存来存储常量表达式的值,而是将常量表达式的值和代码合并到一起,放到虚拟地址空间中的代码区。从汇编的角度看,常量表达式的值就是一个立即数,会被“硬编码”到指令中,不能寻址。

关于虚拟地址空间的分区,我们已在《Linux下C语言程序的内存布局》一节中讲到。

总起来说,常量表达式的值虽然在内存中,但是没有办法寻址,所以也不能使用&来获取它的地址,更不能用指针指向它。下面的代码是错误的,它证明了不能用&来获取常量表达式的地址:

  • int *p1 = &(100);
  • int *p2 = &(23 + 45 * 2);

引用也不能指代临时数据

引用和指针在本质上是一样的,引用仅仅是对指针进行了简单的封装。引用和指针都不能绑定到无法寻址的临时数据,并且 C++ 对引用的要求更加严格,在某些编译器下甚至连放在内存中的临时数据都不能指代。

下面的代码中,我们将引用绑定到了临时数据:

  • typedef struct{
  • int a;
  • int b;
  • } S;
  • int func_int(){
  • int n = 100;
  • return n;
  • }
  • S func_s(){
  • S a;
  • a.a = 100;
  • a.b = 200;
  • return a;
  • }
  • //这里用到了一点新知识,叫做运算符重载,我们会在《运算符重载》一章中详细讲解
  • S operator+(const S &A, const S &B){
  • S C;
  • C.a = A.a + B.a;
  • C.b = A.b + B.b;
  • return C;
  • }
  • int main(){
  • //下面的代码在GCC和Visual C++下都是错误的
  • int m = 100, n = 36;
  • int &r1 = m + n;
  • int &r2 = m + 28;
  • int &r3 = 12 * 3;
  • int &r4 = 50;
  • int &r5 = func_int();
  • //下面的代码在GCC下是错误的,在Visual C++下是正确的
  • S s1 = {23, 45};
  • S s2 = {90, 75};
  • S &r6 = func_s();
  • S &r7 = s1 + s2;
  • return 0;
  • }

第 28~33 行代码在 GCC 和 Visual C++ 下都不能编译通过,第 38~39 行代码在 Visual C++ 下能够编译通过,但是在 GCC 下编译失败。这说明:

  • 在 GCC 下,引用不能指代任何临时数据,不管它保存到哪里;
  • 在 Visual C++ 下,引用只能指代位于内存中(非代码区)的临时数据,不能指代寄存器中的临时数据。

引用作为函数参数

当引用作为函数参数时,有时候很容易给它传递临时数据。下面的 isOdd() 函数用来判断一个数是否是奇数:

  • bool isOdd(int &n){
  • if(n/2 == 0){
  • return false;
  • }else{
  • return true;
  • }
  • }
  • int main(){
  • int a = 100;
  • isOdd(a); //正确
  • isOdd(a + 9); //错误
  • isOdd(27); //错误
  • isOdd(23 + 55); //错误
  • return 0;
  • }

isOdd() 函数用来判断一个数是否为奇数,它的参数是引用类型,只能传递变量,不能传递常量或者表达式。但用来判断奇数的函数不能接受一个数字又让人感觉很奇怪,所以类似这样的函数应该坚持使用值传递,而不是引用传递。

下面是更改后的代码:

  • bool isOdd(int n){ //改为值传递
  • if(n/2 == 0){
  • return false;
  • }else{
  • return true;
  • }
  • }
  • int main(){
  • int a = 100;
  • isOdd(a); //正确
  • isOdd(a + 9); //正确
  • isOdd(27); //正确
  • isOdd(23 + 55); //正确
  • return 0;
  • }

 

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门