(十)C++中的左值lvalue右值rvaue

news2025/7/28 18:34:59

文章目录

    • 1.C++中的变量名是如何存储及引用
    • 2.C++中的左值与右值
    • 3.右值引用
    • 4.移动语义move函数
    • 参考文献


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


1.C++中的变量名是如何存储及引用

int a = 0;

如上,在C++中声明一个变量时,可以知道a是一个int类型的变量占用4个字节,其值为0,这里就有个疑问,如果a表示的变量占用了4个字节是其值的内存空间,那么a本身存储在哪里了呢?

在知乎上有这个问题,下面的回答内容也很好。简单来说就是,对于C/C++这种需要预处理/编译/汇编/链接的翻译成机器代码的语言,变量名不需要储存,只是为了方便程序员编程,在编译器编译时会确定每个变量的地址,所有的局部变量读写都会变成(栈地址 + 偏移)的形式。

int main() {
    int a = 0;
    return 0;
}

得到对应汇编代码如下:

(gdb) disassemble /m main
Dump of assembler code for function main:
   0x0000555555555129 <+0>:	    endbr64 
   0x000055555555512d <+4>:	    push   %rbp
   0x000055555555512e <+5>:	    mov    %rsp,%rbp
   0x0000555555555131 <+8>:	    movl   $0x1,-0x4(%rbp)
   0x0000555555555138 <+15>:    mov    $0x0,%eax
   0x000055555555513d <+20>:    pop    %rbp
   0x000055555555513e <+21>:    retq   
End of assembler dump.

可见只有指令,并没有变量的声明。当使用引用时,

int main() {
    int a = 0;
    int &b = a;
    return 0;
}

得到对应汇编代码如下:

(gdb) disassemble /m main
Dump of assembler code for function main:
   0x0000000000001149 <+0>:	    endbr64 
   0x000000000000114d <+4>:	    push   %rbp
   0x000000000000114e <+5>:	    mov    %rsp,%rbp
   0x0000000000001151 <+8>:	    sub    $0x20,%rsp
   0x0000000000001155 <+12>:    mov    %fs:0x28,%rax
   0x000000000000115e <+21>:    mov    %rax,-0x8(%rbp)
   0x0000000000001162 <+25>:    xor    %eax,%eax
   0x0000000000001164 <+27>:    movl   $0x1,-0x14(%rbp)
   0x000000000000116b <+34>:    lea    -0x14(%rbp),%rax
   0x000000000000116f <+38>:    mov    %rax,-0x10(%rbp)
   0x0000000000001173 <+42>:    mov    $0x0,%eax
   0x0000000000001178 <+47>:    ov    -0x8(%rbp),%rdx
   0x000000000000117c <+51>:    xor    %fs:0x28,%rdx
   0x0000000000001185 <+60>:    je     0x118c <main+67>
   0x0000000000001187 <+62>:    callq  0x1050 <__stack_chk_fail@plt>
   0x000000000000118c <+67>:    leaveq 
   0x000000000000118d <+68>:    retq   
End of assembler dump.

当使用指针时,

int main() {
    int a = 0;
    int *b = &a;
    return 0;
}

得到对应汇编代码如下:

(gdb) disassemble /m main
Dump of assembler code for function main:
   0x0000000000001149 <+0>:	    endbr64 
   0x000000000000114d <+4>:	    push   %rbp
   0x000000000000114e <+5>:	    mov    %rsp,%rbp
   0x0000000000001151 <+8>:	    sub    $0x20,%rsp
   0x0000000000001155 <+12>:	mov    %fs:0x28,%rax
   0x000000000000115e <+21>:	mov    %rax,-0x8(%rbp)
   0x0000000000001162 <+25>:	xor    %eax,%eax
   0x0000000000001164 <+27>:	movl   $0x1,-0x14(%rbp)
   0x000000000000116b <+34>:	lea    -0x14(%rbp),%rax
   0x000000000000116f <+38>:	mov    %rax,-0x10(%rbp)
   0x0000000000001173 <+42>:	mov    $0x0,%eax
   0x0000000000001178 <+47>:	mov    -0x8(%rbp),%rdx
   0x000000000000117c <+51>:	xor    %fs:0x28,%rdx
   0x0000000000001185 <+60>:	je     0x118c <main+67>
   0x0000000000001187 <+62>:	callq  0x1050 <__stack_chk_fail@plt>
   0x000000000000118c <+67>:	leaveq 
   0x000000000000118d <+68>:	retq   
End of assembler dump.

是的,没有看错,使用指针和引用得到了相同的汇编代码,

在这里插入图片描述

同样可以比较,函数里面用引用传参和指针传参,生成的汇编代码也是一样的。

// by pointer
void func1(int &a){}
int main() {
    int b = 0;
    int &a = b;
    func1(a);
    return 0;
}

// by reference
void func1(int *a){}
int main() {
    int b = 0;
    int *a = &b;
    func1(a);
    return 0;
}

因此,从汇编的角度来看,引用跟指针实际上就是同一个东西。引用和指针,更多的是编程语言语法方面的设计,也就是由编译器搞出来的概念,实际上它们最后生成的汇编代码是一样的。而只所以引入引用,据说是为了加运算符重载,没有引用的话,前自增<return type> operator ++(class T)的语义就难以说明清楚。必须在声明引用的同时就要对它初始化,并且,引用一经声明,就不可以再和其它对象绑定在一起了,这和指针常量int * const p有极大的相似之处。引用的一个优点是它一定不为空,因此相对于指针,它不用检查它所指对象是否为空,这增加了效率

2.C++中的左值与右值

左值和右值是C++中的基本概念,简单来说,左值就是一个表达式等号左边的部分,右值就是等号右边的部分。如:

int a = 10; // a 是左值,10是右值
int b = a;  // b 是左值,a自动转为右值

如上,其实左值表示的是对象的引用,而右值正是被左值指向的对象。像变量名/数组下标/返回引用类型函数的返回值等都是左值,左值有对应确定的存储区域,因此可以取其地址。

而像字符串/数字/运算符或函数的运算结果都是右值,右值不需要在内存中存储,只是程序执行中的中间结果,无法寻址。

只有左值才能用在赋值表达式的左边,而右值必须和一个表达式的逻辑对应,因此只能存在于赋值表达式右边。像取地址符&/自增运算符++/自减运算符--都需要左值作为其参数。

  • 返回引用的函数的调用产生的是左值,返回值的函数调用产生的是右值

    #include <cstdio>
    #include <cstring>
    
    void printStr(std::string &s) {
        printf("%s\n", s.c_str());
    }
    
    std::string getValueString() {
        std::string s = "getValueString";
        return s;
    }
    
    std::string s;
    
    std::string & getRefString() {
        s = "getRefString";
        return s;
    }
    
    int main() {
        getValueString() += " <==> ";// error, getValueString() is lvalue
        printfStr(getValueString()); // error, getValueString() is lvalue
        
        getRefString() += " <==> ";// correct, getRefString() is rvalue
        printfStr(getValueString()); // correct, getValueString() is rvalue
        // getRefString <==> getRefString
    }
    
    
  • 左值可以自动隐式转为右值,但右值无法隐式转为左值

    int a = 10;
    int b = a; // a is converted implicitly to an rvalue 
    

3.右值引用

以上介绍的引用即通常所说的引用,是指左值引用lvalue reference,而2011年08月份发布的C++11中引入右值引用rvalue reference。在定义左值引用时都是定义变量的引用,而不能定义一个指向临时数据的左值引用,这可以使用右值引用来实现。

int a = 1;
int &b = a; // lvalue reference, correct

int &b = 1; // error, lvalue reference can not refer to a temporary value;

int *a = &10; // error: lvalue required as unary ‘&’ operand

此时,可以通过&&的方式定义指向临时值的右值引用:

int x = 1;
int &&b = 1;
int &&c = x; // 错误,x是一个左值
int &&d = b; // 错误,b是一个左值

将以下代码转成汇编对比的结果为:

// 一级指针
int main() {
    int a = 10;
    int *b = &a;
    return 0;
}

// 右值引用
int main() {
    int a = 10;
    int &&b = 10;
    return 0;
}

在这里插入图片描述

对比以上的代码可以看到,使用右值引用时,汇编代码几乎和一级指针相同,除了多出两行:

0x000000000000116b <+34>:	mov    $0xa,%eax
0x0000000000001170 <+39>:	mov    %eax,-0x18(%rbp)

右值引用相当于创建了临时指针-0x18用来存放10,在一级指针中使用的时a的地址-0x1给指针赋值,在右值引用中使用系统自动生成的变量地址-0x18p赋值,因此右值引用实际上就是一级指针,只是在语言层面的语义区分,其底层实现仍然是借用指针。

了解了右值引用后,再看#2中printStr函数,要想打印非返回类型为值的函数调用的结果,可以借用右值引用:

#include<cstdio>
#include<string>
std::string getStr() {
    std::string s = "rvalue";
    return s;
}

void printStr(std::string &&str) {
    printf("%s", str.c_str());
}

int main() {
    printStr(getStr()); // correct
    return 0;
}

4.移动语义move函数

C++11标准库在头文件utility中,引入了std::move函数,其目的是提高程序运行的效率,把以前一些需要“先拷贝,再删除源对象”的操作,转化为直接把源对象移动到目标位置,如STL容器中的push_back操作等。

template< class T >
constexpr std::remove_reference_t<T>&& move( T&& t ) noexcept;

从函数声明可以看出,move返回的是std::remove_reference_t类型的右值引用,std::move通常用来表示一个对象有可能是从对象t移动过去的,实现了对象资源的高效传递,避免了对象的销毁重建,其作用等同于将对象static_cast类型转换为一个右值引用rvalue reference

如下,在std::string类中,其第9个构造函数是带noexcept修饰的move constructor,可以实现将资源从一个string直接移动到另一个string,减少资源的复制删除。

#include<cstdio>
#include<string>
#include<vector>

void printStr(std::string &str) {
    printf("%s\n", str.c_str());
}

int main() {
    std::string str = "this";
    // move (9)	string (string&& str) noexcept;
    std::string s(std::move(str));
    printStr(str);
    printStr(s); // content str is moved to s, str will be empty
    // std::string &&s = std::move(str);
    
    // 通过move实现资源的移动,减少复制
    std::vector<std::string> v;
    v.push_back(std::move(s));
    printStr(v[0]); 
    printStr(s); // s empty
}   

通过实验对比,使用std::movevectorpush_back string 10000次,性能相差近4倍。

#include <cstdio>
#include <cstring>
#include <vector>
#include<opencv2/opencv.hpp>
int main()
{
    std::vector<std::string> v;
    // take 9.26ms
    long st = cv::getTickCount();
    for(int i = 0; i < 10000; i++)
    {
        std::string s = "this";
        v.push_back(s);
    }
    long et = cv::getTickCount();
    printf("%.5f\n", (float)(et - st) / cv::getTickFrequency());
    v.clear();
    // take 2.35ms
    st = cv::getTickCount();
    for(int i = 0; i < 10000; i++)
    {
        std::string s = "this";
        v.push_back(std::move(s));
    } 
    et = cv::getTickCount(); 
    printf("%.5f\n", (float)(et - st) / cv::getTickFrequency());  
}

参考文献

  • 1.https://www.zhihu.com/question/34266997#:~:text=%E5%8F%98%E9%87%8F%E5%90%8D%E6%98%AF%E7%BB%99%E7%BC%96%E8%AF%91,%E4%B8%AA%E7%AC%A6%E5%8F%B7%E5%AF%B9%E5%BA%94%E4%B8%80%E4%B8%AA%E5%9C%B0%E5%9D%80%E3%80%82
  • 2.https://www.xianwaizhiyin.net/?p=2763
  • 4.https://learning.oreilly.com/library/view/c-in-a/059600298X/
  • 4.https://www.zhihu.com/question/26203703

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/16376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【0143】 System V共享内存(Shared Memory)

文章目录 1. 共享内存1.1 共享内存分类2. System V IPC密钥3. System V 共享内存调用3.1 shmget()3.2 shmat()3.3 shmdt()3.4 shmctl()4. 实战演练4.1 服务端程序代码实现4.2 客户端程序代码实现1. 共享内存 共享内存是 Linux 和其他类 Unix 系统下可用的三种进程间通信 (IPC)…

Docker下安装Zookeeper以及Kafka

一、安装Zookeeper 1. 查看Zookeeper镜像以及版本 访问Zookeeper镜像库地址 也可以通过命令docker search zookpper在命令行搜索zookeeper镜像&#xff0c;但是通过该命令无法查看远程镜像版本信息。 NAME&#xff1a;镜像名(镜像仓库源的名称) DESCRIPTION&#xff1a;对该镜…

yolov5剪枝实战2:网络剪枝原理介绍

1. 网络轻量化相关技术 网络轻量化的相关技术分类:1. 网络剪枝(Network pruning) 2. 稀疏表示(Sparse representation) 3. Bits precision(低比特表示,比如不用浮点型,使用int量化) 4. Kownledge distillation(知识蒸馏) 2. 网络剪枝 神经网络一般都是over-parameterized,…

SharePoint Integrator Delphi版

SharePoint Integrator Delphi版 SharePoint Integrator包含易于使用的组件&#xff0c;用于连接流行的SharePoint Server结构&#xff0c;如网站、列表和文档。SharePoint Integrator组件支持对SharePoint对象的访问&#xff0c;允许应用程序轻松地查询和修改列表、访问托管文…

京东发布第三季度财报员工总数近50万 “以实助实”助力高质量就业

11月18日&#xff0c;京东集团&#xff08;纳斯达克股票代码&#xff1a;JD&#xff0c;港交所股票代号&#xff1a;9618&#xff09;发布了2022年三季度业绩。其中净收入为2435亿元人民币&#xff0c;同比增速高于同期国内社会消费品零售总额3.5%的增速&#xff1b;其中&#…

【17-微服务网关之Spring Cloud GatewaySpring Cloud Gateway网关服务搭建】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了&#xff0c;请点击这里&#xff01;】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

“知识图谱补全”术语:知识图谱补全、三元组分类、链接预测

基本简介&#xff1a; 知识图谱补全通常定义为“三元组分类”或“链接预测”任务。首先&#xff0c;一条知识在知识图谱中通常由三元组表示&#xff1a;“头实体&#xff0c;关系&#xff0c;尾实体”。三元组分类即对于给定的三元组&#xff0c;预测其正确的概率。而链接预测…

VM虚拟机卡顿、闪退一系列问题与卸载重装问题(详细版)

最新发现好多虚拟机有闪退现象&#xff0c;对此我给出一系列解决方案&#xff0c;仅供参考 一.软件问题 1.虚拟机闪退 首先&#xff0c;如果是VMware Workstation Pro也就是软件本身闪退问题&#xff0c;即还没有运行或打开系统时就已经闪退&#xff0c;说明软件有问题&…

Windows系统VirtualBox下载与安装

Windows系统VirtualBox下载与安装 一、下载&#xff1a;https://www.virtualbox.org/wiki/Downloads 1.安装包 2.扩展包&#xff08;对USB 2.0、USB 3.0、远程桌面协议 VRDP等实用功能的支持&#xff09; 二、安装 1.如果安装出现本机缺少必要包的情况&#xff0c;不要慌&am…

3.1版本【HarmonyOS 第一课】正式上线!参与学习赢官方好礼>>

【课程介绍】《HarmonyOS第一课》是跟随版本迭代不断推出的系列化课程&#xff0c;本期课程基于HarmonyOS 3.1版本的新技术和特性&#xff0c;每个课程单元里面都包含视频、Codelab、文章和习题&#xff0c;帮助您快速掌握HarmonyOS的应用开发&#xff0c;快速了解新的特性和技…

基于PHP+MySQL汽车展览会网站的设计与实现

随着我国经济的发展,汽车已经进入了家家户户,但是很多时候人们因为各种原因需要进行汽车出行,但是很多时候人们有不知道购买什么样的汽车更适合自己,如果挨个去汽车4S既浪费时间又不能够把各类汽车都看全,人们更希望通过发达的车展来查询自己所需的汽车信息,本系统也是出于这样…

分享几个常用的可以从外部攻击视角发现甲方公司安全问题的开源工具

资产管理平台-ARL https://github.com/TophantTechnology/ARL 作为甲方&#xff0c;一定要有自己的资产平台&#xff0c;主要目的就是基于外部攻击视角不断发现风险&#xff0c;当然放在内网也可以&#xff0c;测试了很多开源的工具&#xff0c;最终还是选择了ARL&#xff0c;…

【深入理解C++】左值引用、常引用、右值引用、std::move()函数

文章目录1.左值引用2.常引用2.1 const引用可以指向临时数据2.2 const引用可以指向不同类型的数据2.3 const引用作为函数参数3.右值引用4.std::move()函数1.左值引用 左值引用只能绑定到左值上&#xff0c;不能绑定到右值上。 左值引用不能绑定到临时变量上&#xff0c;因为临…

是德N5290A矢量网络分析仪技术参数及操作步骤

网络分析仪能对被测量器件(主要分为有源元件和无源元件)的线性和非线性特性(幅频特性&#xff0c;相频特性&#xff0c;时频特性&#xff0c;功率频率特性等)进行表征。 那么大家真的了解网络分析仪吗?下面西安安泰测试以是德科技(安捷伦)E50系列矢量网络分析仪为例介绍仪器基…

基于PHP+MySQL汽车租赁管理系统的设计与实现

虽然汽车已经进入了家家户户,但是很多时候人们因为各种原因需要进行汽车的租赁,可能是到达的城市比较远,也可能是经济实力不允许,或者是对车辆的要求不高,暂时没必要买车等,这些特殊的原因导致了汽车租赁行业的兴起。越来越多的用车用户希望通过租赁来直接获取使用的使用权,这样…

四、C++ 类的继承

文章目录一、引言1.1 类成员的三种类型1.2 访问控制和继承二、类的继承2.1 格式2.2 继承类型三、多继承一、引言 1.1 类成员的三种类型 我们知道&#xff0c;在类中一共有三种类型&#xff0c;分别为public&#xff0c;private和protected&#xff0c;默认为private&#xff…

redhat6.5安装nvidia驱动

iso&#xff1a;redhat6.5 1、下载安装NVIDIA显卡驱动 1.1 打开终端 1.2 输入下方代码进行查看自己的显卡型号 lspci |grep -i nvidia 2、在官网下载对应的驱动&#xff1a; 官方驱动 | NVIDIA 3、禁用nouveau驱动 3.1 禁用默认的nouveau&#xff0c;据说这是NVIDIA显卡…

yolox原理

目录 1 Decoupled Head 1.1 YOLOX的解耦头结构思考 2 Data Augmentation 别人讲的好的文章&#xff0c;yolox作者写的 以下正文部分内容&#xff0c;参考该链接 如何评价旷视开源的YOLOX&#xff0c;效果超过YOLOv5? - 知乎感谢大家对旷视开源的 YOLOX 关注&#xff0c;本…

数字化外协生产综合管理系统,实现信息自动同步,数据自动统计分析!

随着市场经济的不断发展&#xff0c;制造生产行业竞争不断加剧&#xff0c;精细化、无纸化办公已成为生产企业生存和发展的基本条件。要想将企业内部管理做的更精更细&#xff0c;就必须借助于现代先进的企业管理手段和工具&#xff0c;如企业资源计划系统、生产管理系统等。 …

Linux安装Apache(解压版)

安装依赖 yum -y install gcc perl wget make pcre-devel openssl-devel expat-devel 安装目录 mkdir /opt/apache && cd /opt/apache 安装包下载 wget https://dlcdn.apache.org/apr/apr-1.7.0.tar.gz --no-check-certificate wget https://dlcdn.apache.org/apr/apr-…