从汇编层看64位程序运行——C++的Copy Elision(复制省略)技术的实现

news2024/9/25 23:17:21

大纲

  • Copy Elision的应用场景
    • 返回值优化(Return Value Optimization, RVO)
    • 命名返回值优化(Named Return Value Optimization, NRVO)
    • C++11及以后的移动语义
  • 禁用Copy Elision(复制省略)
  • Copy Elision(复制省略)的底层实现
    • 对象构造于caller还是callee
    • 对象析构于caller还是callee
    • 对象的构造和析构是否在一个过程(caller/callee)中
    • 对象的地址空间位于caller还是callee的栈帧
  • 注意事项
  • 总结

在C++中,Copy Elision(复制省略)是一种编译器优化技术,它允许编译器在特定情况下省略不必要的对象复制或移动操作,以提高程序的效率。这种优化技术尤其适用于那些涉及到临时对象(如函数返回值或作为函数参数传递的对象)的场景。

Copy Elision的应用场景

返回值优化(Return Value Optimization, RVO)

当函数返回一个对象时,如果没有使用命名返回值(即没有显式地声明一个变量作为返回值,而是直接在返回语句中构造了对象),编译器可以优化掉这个对象到调用者之间的复制或移动操作。

Custom craeteCustom() {
    return Custom(1);
}

int main(int argc, char* argv[]) {
    Custom custom = craeteCustom();
    return 0;
}

比如上面的代码,理论上说,craeteCustom在其栈帧上分配了一个空间,然后返回到main函数中。由于createCustom和main函数有不同的栈帧,这个位于createCustom栈帧上的Custom对象会通过复制/移动构造函数复制/移动到main函数的栈帧中。

但是现代C++编译器使用Copy Elision(复制省略)技术不进行复制/移动构造函数的调用,而使用其他方法达成上述目的。

我们可以使用下面代码测试

#include <iostream>

class Custom {
public:
    Custom(int value) : value(value) {
        std::cout << "Constructor Custom(" << value << "). this = " << this << std::endl;
    }
    
    Custom(const Custom& custom) : value(custom.value) {
        std::cout << "Copy constructor Custom(" << custom.value << "). this = " << this << " from " << &custom << std::endl;
    }

    Custom& operator=(const Custom& custom) {
        value = custom.value;
        std::cout << "Copy assignment operator Custom(" << custom.value << "). this = " << this << " from " << &custom << std::endl;
        return *this;
    }

    Custom(Custom&& custom) : value(custom.value) {
        std::cout << "Move constructor Custom(" << custom.value << "). this = " << this << " from " << &custom << std::endl;
    }

    Custom& operator=(Custom&& custom) {
        value = custom.value;
        std::cout << "Move assignment operator Custom(" << custom.value << "). this = " << this << " from " << &custom << std::endl;
        return *this;
    }

    ~Custom() {
        std::cout << "Destructor Custom(" << value << "). this = " << this << std::endl;
    }

    friend std::ostream& operator<<(std::ostream& os, const Custom& custom) {
        os << custom.value;
        return os;
    }

public:
    int get_value() const {
        return value;
    }

    void set_value(int value) {
        this->value = value;
    }

private:
    int value;
};

Custom craeteCustom() {
    return Custom(1);
}

int main(int argc, char* argv[]) {
    Custom custom = craeteCustom();
    std::cout << "main custom = " << custom << ".Address is " << &custom << std::endl;
    return 0;
}

其输出结果如下
在这里插入图片描述
可以看到这段代码并没有进行复制/移动构造函数的调用。

命名返回值优化(Named Return Value Optimization, NRVO)

即使函数使用了命名返回值,如果编译器能够确定这个命名对象在整个函数体内没有除了返回之外的用途,它仍然可以进行优化,省略掉返回时的复制或移动操作。但值得注意的是,NRVO并非所有编译器都保证会进行,它的实现依赖于编译器的具体实现。

我们对上述案例做如下修改,在craeteCustom中先生成一个命名变量,然后将其返回。

Custom craeteCustom() {
    Custom custom = Custom(1);
    return custom;
}

针对这种操作,NRVO也会生效,并不会触发复制/移动构造函数将这个对象复制/移动到main函数栈帧中。
在这里插入图片描述

C++11及以后的移动语义

在C++11及以后的版本中,引入了移动语义和std::move函数,这为减少对象复制提供了新的工具。尽管这本身不是Copy Elision的一部分,但它与Copy Elision密切相关,因为移动操作通常比复制操作要快得多。在某些情况下,编译器可能会选择移动操作来代替复制操作,但这仍然属于Copy Elision的范畴,因为它旨在减少不必要的对象拷贝开销。

禁用Copy Elision(复制省略)

针对上面这个例子,可以针对CMakelists.txt增加-fno-elide-constructors参数。它会让编译器禁用Copy Elision(复制省略)技术。

cmake_minimum_required(VERSION 3.12)
# 项目信息
# 最后一级目录为项目名称
get_filename_component(ProjectName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(${ProjectName})

# 添加可执行文件
add_executable(${ProjectName} main.cpp)

target_compile_options(${ProjectName} PRIVATE -fno-elide-constructors)
Custom craeteCustom() {
    Custom custom = Custom(1);
    return custom;
}

禁用后,编译器会采用移动构造函数的方式,将craeteCustom中创建的对象移动到main函数中。
在这里插入图片描述

Copy Elision(复制省略)的底层实现

高级的语言和编译器封装了很多底层实现。我们需要拨云见日,通过几个实验来回答我们对其的疑问。

这个时候我们就要抛开原来的C++代码,从汇编层看其真实实现。

由于C++编译器会使用一定的规则替换掉函数或者变量的符号名称,所以我们需要借助《C++拾趣——转换编译器生成的类型名为代码中的类型名》中的方案,将有关函数名进行转换。
在这里插入图片描述

对象构造于caller还是callee

我们分别看caller(main)和callee(craeteCustom)的反汇编
在这里插入图片描述
可以看到,Custom的构造操作和代码中表达的一致——构造于callee(craeteCustom)

所以网上有些说法认为“caller直接构造对象”是错误的。

对象析构于caller还是callee

我们还是分析caller(main)和callee(craeteCustom)的反汇编
在这里插入图片描述
可以看到析构函数是在caller(main)中被调用

对象的构造和析构是否在一个过程(caller/callee)中

通过上面的反汇编分析,我们可以看到:Copy Elision(复制省略)技术让对象的构造和析构不发生在同一个过程中:构造发生在callee(craeteCustom),析构发生在caller(main)中

对象的地址空间位于caller还是callee的栈帧

我们继续追踪caller(main)和callee(craeteCustom)的反汇编。

在craeteCustom中,+25行rax寄存器保存的是我们需要构造的Custom对象的this指针。追踪rax寄存器值的来源,最终可以追踪到rdi寄存器上。而rdi寄存器的值是main函数调用craeteCustom前设置的。
在这里插入图片描述
通过下图可以发现,rdi寄存器的值最终指向main函数的的rbp-0x1c地址。这个就说明我们在callee(craeteCustom)中构造的对象位于main函数的栈帧上
在这里插入图片描述

注意事项

程序员通常不需要显式地指示编译器进行Copy Elision,这是编译器自动进行的优化。
然而,为了充分利用这一优化,程序员在编写代码时应该考虑使用按值返回(而非按引用或指针返回)和避免不必要的对象拷贝。
值得注意的是,Copy Elision并非总是可以进行,特别是在涉及复杂表达式或库函数调用的上下文中。在这些情况下,程序员可能需要手动优化代码或使用其他技术来减少不必要的拷贝开销。
总之,Copy Elision是C++中一个重要的编译器优化技术,它有助于减少程序中不必要的对象拷贝开销,从而提高程序的性能和效率。通过理解这一技术的工作原理和应用场景,程序员可以编写出更高效、更易于维护的C++代码。

总结

  • Copy Elision(复制省略)技术构造的对象位于caller的栈帧中。
  • Copy Elision(复制省略)技术构造的对象在callee中构造。
  • Copy Elision(复制省略)技术构造的对象在calller中析构。
  • Copy Elision(复制省略)技术将预先分配好的地址通过rdi寄存器传递给callee进行对象构造。

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

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

相关文章

Redis 的 主从复制

目录 1 Redis 主从复制介绍 2 Redis主从复制原理 2.1 主从同步过程 3 Redis实现主从复制 3.1 环境配置 3.2 修改各节点的配置文件 3.2.1 MASTER 3.2.2 SLAVE 3.3.3 重启Redis 3.3 查看是否实现了主从复制 3.3.1 MASTER 3.3.2 SLAVE 3.3.3 Redis 常用操作 3.3.4 数据添加查看…

AD9162数据链路lanes与FPGA高速BANK引脚交叉相连导致vivado编译失败

问题概述 对AD9162芯片进行功能开发时&#xff0c;发现AD9162的链路lanes与FPGA的高速BANK引脚存在交叉相连的情况&#xff0c;按照实际的引脚连接关系进行约束后&#xff0c;vivado编译失败。 问题阐述及原因分析 问题详情阐述 板卡对AD9162芯片进行功能开发时&#xff0c;发…

高并发业务下的库存扣减技术方案设计

扣减库存需要查询库存是否足够: 足够就占用库存不够则返回库存不足&#xff08;这里不区分库存可用、占用、已消耗等状态&#xff0c;统一成扣减库存数量&#xff0c;简化场景&#xff09; 并发场景&#xff0c;若 查询库存和扣减库存不具备原子性&#xff0c;就可能超卖&…

node.js使用express框架实现api接口开发(从零开始,超简单可直接复制)

目录 一、效果图 二、实现 1、引入express框架依赖 2、 新建启动文件&#xff08;/server/index.js&#xff09; 3、新建接口函数文件&#xff08;/server/router.js&#xff09; 一、效果图 二、实现 1、引入express框架依赖 在项目文件夹根目录下&#xff0c;打开控制台…

ShenNiusModularity:一款基于 .NET Core 框架研发的自媒体内容管理系统

项目介绍 ShenNiusModularity是一款基于 .NET Core 框架研发的、开源、免费的自媒体内容管理系统。项目秉承大道至简的原则开发&#xff0c;坚持业务模块最低复杂度复用&#xff0c;代码方面追求简单、高效、实用。适合小白入门进阶&#xff0c;同样适用老手上路干活。 项目包…

Python青少年简明教程:字符串

Python青少年简明教程&#xff1a;字符串 字符串&#xff08;string&#xff09;是用于表示文本的数据类型。它是不可变的序列类型&#xff0c;即一旦创建&#xff0c;字符串中的字符就无法改变。 下面对Python中字符串的详细介绍&#xff0c;包括字符串的创建、操作和常见方法…

MySQL 集群技术全攻略:从搭建到优化(下)

目录 四.mysql高可用之组复制 (MGR) 1.组复制流程 2.组复制单主和多主模式 3.实现mysql组复制 五.mysql-router&#xff08;mysql路由&#xff09; 1.Mysql route的部署方式 六.mysql高可用之MHA 1.MHA架构图 2.为什么要用MHA&#xff1f; 3.MHA 的组成 4.什么是 MH…

网络 (tcp)

客户端 /*************************************************************************> File Name: client.c> Author: yas> Mail: rage_yashotmail.com> Created Time: Thu 22 Aug 2024 04:04:26 PM CST******************************************************…

从零开始学习SLAM六(单应矩阵)

本文参考&#xff1a;计算机视觉life 概念 单应性&#xff08;homography&#xff09;是指两个平面之间的一种保直线性的对应关系。如果一个平面上的点集经过某种变换后&#xff0c;在另一个平面上形成的新点集仍然保持原来的线性特性&#xff08;如共线的点仍然共线&#xf…

一起搭WPF界面之View的简单设计一

一起搭WPF界面之View的简单设计一 1 前言2 界面预期设想3 基础的实现步骤3.1 界面划分3.1.1 基础框架代码&#xff1a;3.1.2 实现效果 4 界面花样设计4.1 花样设计4.2 界面源代码4.3 错误提醒4.3.1 错误14.3.2 错误2 总结 1 前言 基于上一篇的window、Gird、Border的简单介绍&…

XSS LABS - Level 13 过关思路

关注这个靶场的其他相关笔记&#xff1a;XSS - LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;过关流程 进入靶场&#xff0c;老样子&#xff0c;右击&#xff0c;查看页面源码&#xff0c;找找不同&#xff1a; 可以看到&#xff0c;本关又多了一个新字段 t_cook&#xf…

关于 Vue/React 的 cli 中运用 webpack 打包的原理简单解析

webpack、webpack-cli的打包 关于 webpack 对前端工程中进行资源文件进行打包处理的过程中&#xff0c;运用到的核心插件主要是 webpack 和 webpack-cli&#xff0c;在 react 和 vue 对于打包各自工程中的 cli 则是进行了自定义的构建&#xff0c;专门用于项目打包的 …

【MySQL-25】万字总结<锁>——(全局锁&行级锁&表级锁)【共享锁,排他锁】【间隙锁,临键锁】【表锁,元数据锁,意向锁】

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

Bytebase 2.22.2 - 允许在工作空间为群组分配角色

&#x1f680; 新功能 允许在工作空间给群组分配角色。 支持禁用邮箱密码登录&#xff0c;仅允许 SSO 登录的设置项。 新增 Postgres SQL 审核规则&#xff1a;禁止在列上设置会变化的默认值。 &#x1f514; 重大变更 下线项目内的变更历史页面&#xff1b;所有变更历史仍可…

Phoenix

Apache Phoenix 是一个开源的关系数据库层&#xff0c;运行在 Apache HBase 之上&#xff0c;旨在为 HBase 提供 SQL 查询能力和优化的存储引擎。它允许用户使用标准的 SQL 查询和事务语义来管理 HBase 中的数据&#xff0c;并且可以与现有的大数据生态系统无缝集成。Phoenix 通…

IOS开发 铃声制作(库乐队)

IOS开发&#xff0c;实现铃声制作功能。 在IOS端&#xff0c;要设置铃声都是通过库乐队来制作的。 先看一下库乐队中铃声的文件结构。下面是弄的一个示例的文件&#xff0c;文件信息如下&#xff1a; 我们右击文件&#xff0c;点击显示包内容如下&#xff1a; 能看到一个aiff格…

解决ssl certificates updated-生成环境中的实例

应原来小伙伴的要求&#xff0c;生产环境出错了&#xff0c;是harbor的ssl cert过期了&#xff0c;也因为使用的是免费的ssl证书&#xff0c;现在无法正常使用harbor&#xff0c;所以贴来了2023年1月曾经搭建的文档&#xff0c;希望能解决问题。^v^. -------------------------…

25 filedialog组件

Tkinter filedialog 组件使用指南 Tkinter 的 filedialog 组件提供了一个图形界面&#xff0c;用于打开和保存文件。它允许用户通过标准的文件选择对话框来选择文件&#xff0c;非常适合需要文件操作的GUI应用程序。以下是对 filedialog 组件的详细说明和一个使用案例。 file…

爆改YOLOv8 |YOLOv8融合SEAM注意力机制

1&#xff0c;本文介绍 SEAM&#xff08;Spatially Enhanced Attention Module&#xff09;是一个注意力网络模块&#xff0c;旨在解决面部遮挡导致的响应损失问题。通过使用深度可分离卷积和残差连接的组合&#xff0c;SEAM模块增强未遮挡面部的响应。深度可分离卷积在每个通…

Xshell 连接 Ubuntu 服务器失败问题(Connection failed)

目录 Xshell 连接 Ubuntu 服务器失败问题&#xff08;Connection failed&#xff09; 1.查看Ubuntu中是否安装 sshd 2.在Ubuntu中安装sshd 3.需要打开Ubuntu中新安装的sshd 4.在检查Ubuntu中sshd是否安装成功 5.临时关闭Ubuntu中的防火墙 6.Xshell 连接 Ubuntu 服务器成…