探讨C++ 变量生命周期、栈分配方式、类内存布局、Debug和Release程序的区别(二)...

 

看此文,务必需要先了解本文讨论的背景,不多说,给出链接:

探讨C++ 变量生命周期、栈分配方式、类内存布局、Debug和Release程序的区别(一)

 

本文会以此问题作为讨论的实例,来具体讨论以下四个问题:

(1)       C++变量生命周期

(2)       C++变量在栈中分配方式

(3)       C++类的内存布局

(4)       Debug和Release程序的区别

 

1、Debug版本输出现象解析

先来说说Debug版本的输出,前5次输出,交替输出,后5次输出,交替输出,但是,前5次和后5次的地址是不一样的。

我们来看看反汇编:

              T1 r(2);
01363A0D  push        2   
01363A0F  lea         ecx,[r]
01363A12  call        T1::T1 (1361172h)p[i]=&r;
01363A17  mov         eax,dword ptr [i]
01363A1A  lea         ecx,[r]
01363A1D  mov         dword ptr p[eax*4],ecx

 

关键是看对象r的地址是如何分配的,但是,反汇编中似乎没有明显的信息,只有一句:lea  ecx,[r],这条语句是什么意思呢?将对象r的地址取到通用寄存器ecx中。

我们知道,程序在编译链接的时候,变量相对于栈顶的位置就确定了,称为相对地址确定。所以,此时程序在运行了,根据所在环境,变量的绝对地址也就确定了。

通过lea指令取得对象地址,调用对象的构造函数来进行构造,即语句call  T1::T1 (1361172h). 构造完之后,对象所在地址的值才被正确填充。

 

好了,我们知道了这些局部变量相对于栈的相对地址,其实在编译链接的时候就确定了,那么,这个策略是什么样的呢?就是说,编译器是如何来决定这些局部变量的地址的呢?

一般来说,对于不同的变量,编译器都会分配不同的地址,一般是按照顺序分配的。但是,对于那些局部变量,而且非常明显的生命周期已经结束了,同一个地址,也会分配给不同的变量。

举个例子,地址0X00001110,被编译器用来存放变量A,同时也可能被编译器用来存放变量B,如果A和B的大小相等,并且肯定不会同时存在。

 

编译器在判断一个地址是否能够被多个变量同时使用的时候,这个判断策略取决于编译器本身,不同的编译器判断策略不同。

微软的编译器,就是根据代码的自身逻辑来判断的。当编译器检测到以下代码的时候:

  for(int i=0;i<5;i++){if(i%2==0){T1 r(2);p[i]=&r;cout<<&r<<endl;}else{T2 r(3);p[i]=&r;cout<<&r<<endl;}}

微软的编译器认为,只需要分配两个地址则可,分别用来保存两个对象,循环执行的话,因为前一次生成对象的生命周期已经结束,直接使用原来的地址则可。

因此,我们在用VS编译这段程序时,就出现了地址交替输出的情况。

 

当微软的编译器接着又看到以下代码的时候,

 for(int i=5;i<10;i++){if(i%2==0){T1 r(4);p[i]=&r;cout<<&r<<endl;}else{T2 r(5);p[i]=&r;cout<<&r<<endl;}}

微软的编译器认为,需要再分配两个地址,分别用来保存这两个新的对象,

于是,我们再次看到了地址交替输出的情况,只是这一次交替输出的地址与前一次交替输出的地址不同。

 

延伸1:稍微修改代码再试试

我们已经能够理解VS下Debug版本为什么会输出这样的结果了,再延伸一下,我们把代码进行修改:

  修改前的代码:for(int i=0;i<5;i++){if(i%2==0){T1 r(2);p[i]=&r;cout<<&r<<endl;}else{T2 r(3);p[i]=&r;cout<<&r<<endl;}}
修改后的代码为:if (0 == i){T1 r(2);p[i]=&r;cout << &r << endl;}else if (1 == i){T2 r(3);p[i]=&r;cout << &r << endl;}else if (2 == i){T1 r(2);p[i]=&r;cout << &r << endl;}else if (3 == i){T2 r(3);p[i]=&r;cout << &r << endl;}else if (4 == i){T1 r(2);p[i]=&r;cout << &r << endl;}
)

代码修改之后,功能完全一样,那么前五次循环的输出会有什么不同吗?

也许你猜到了,修改完代码之后,前5次地址输出,是5个不同的地址,按规律递增或者递减。

很明显,代码的改动,编译器的认知也改变了,分配了5个地址来给这5个对象使用。

 

延伸2:GCC编译器是如何编译这段代码的呢?

我们再延伸一下,不同的编译器,对代码的编译是不同的,GCC编译器是如何编译这段代码的呢?默认编译之后,运行结果如下:

 

不用我说,大家也知道了,GCC编译器检测到这些变量生命周期结束了,尽管有十次循环,尽管代码有改动,但是GCC仍然只有分配一个地址供这些变量使用。

理由很简单,变量的生命周期结束了,它的地址自然就可以给其他变量用了,更何况这样变量的大小还是一样的呢!

 

 2、VS下Release版本输出现象解析:

不再延伸,回到正题,VS下Release版本的表现为什么和Debug版本不一样呢?

同样,我们来看原始代码的反汇编:

        if(i%2==0){T1 r(2);p[i]=&r;cout<<&r<<endl;
00C11020  mov         ecx,dword ptr [__imp_std::endl (0C12044h)]
00C11026  push        ecx 
00C11027  mov         ecx,dword ptr [__imp_std::cout (0C12048h)]
00C1102D  test        bl,1
00C11030  jne         main+42h (0C11042h)
00C11032  lea         eax,[esp+14h]
00C11036  mov         dword ptr [esp+14h],ebp
00C1103A  mov         dword ptr [esp+18h],ebp
00C1103E  mov         edx,eax}else
00C11040  jmp         main+50h (0C11050h){T2 r(3);p[i]=&r;
00C11042  lea         eax,[esp+1Ch]
00C11046  mov         dword ptr [esp+1Ch],edi
00C1104A  mov         dword ptr [esp+20h],esicout<<&r<<endl;}

Release版本做了进一步的优化,esp内的值在本程序运行的过程中未曾改变,因此,尽管有十次循环,也只分配了两个对象的空间,即两个地址。

最后,我们看到,前5次循环和后5次循环的交替输出的地址是一样的。

 

3、再提一点:最后的十次输出现象解析:

    for(int i=0;i<10;i++)

    {

        p[i]->showNum();

    }

其实是没有意义的,因为这10个指针指向的对象的生命周期早就结束了。

那么为什么还能输出正确的值呢?因为,这些对象的生命周期虽然结束了,但是这些对象的内存没有遭到破坏,仍还存在,并且数据未被改写。

如果此程序后续还增加代码,这些地址的内容是否会被其他对象占用都是不可知的,所以,请不要使用生命周期已经结束了的对象。

 

4、总结:

给大家建议,C++语言的对象生命周期的概念很重要,要重视,另外,使用指针要注意空指针的问题。

有时候,可以直接使用对象的方式,就不要使用太多指针,都是坑!

 

后记:

突然觉得自己好无聊,以后还是少分析这些问题,多做些实事!不过,偶尔分析一下,还是可以的。

转载于:https://www.cnblogs.com/yylwuwei/p/3148756.html

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

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

相关文章

后台系统可扩展性学习笔记(一)概要

文章目录系统大致架构可扩展性负载均衡器与会话保持引入冗余增强系统可用性缓存减轻数据库压力异步处理参考系统大致架构 当一个用户请求从客户端出发&#xff0c;经过网络传输&#xff0c;达到 Web 服务层&#xff0c;接着进入应用层&#xff0c;最后抵达数据层&#xff0c;它…

后台系统可扩展性学习笔记(二)权衡取舍

文章目录性能与可扩展性延迟与吞吐量可用性与一致性一致性模式可用性模式可用性衡量参考系统设计中也面临许多权衡取舍&#xff1a;性能与可扩展性延迟与吞吐量可用性与一致性 性能与可扩展性 可扩展&#xff0c;意味着服务能以加资源的方式成比例地提升性能&#xff0c;性能…

后台系统可扩展性学习笔记(三)DNS机制原理

文章目录DNS概念梳理域名基本概念资源记录基本概念路由策略DNS 域空间结构实现原理复制机制查询机制缓存机制参考DNS概念梳理 DNS&#xff08;Domain Name System&#xff09;相当于互联网的通讯录&#xff0c;能够把域名翻译成 IP 地址。 从技术角度来讲&#xff0c;DNS 是个…

后台系统可扩展性学习笔记(四)CDN机制原理

文章目录概念梳理CDN拓扑结构CDN内容分发方式架构原理工作原理实现原理概念梳理 CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;是由分布在不同地理位置的代理服务器及其数据中心组成的网络&#xff0c;希望在空间距离上为用户就近提供服务&am…

后台系统可扩展性学习笔记(五)负载均衡

文章目录Load balancer(负载均衡器)请求传输拆解DNS 负载均衡客户端负载均衡OSI 七层模型回顾2 层、3 层负载均衡3/4 层负载均衡7 层负载均衡在 第一节谈到了系统的横向扩展在于从单机扩展到多机&#xff0c;那么面临的第一个问题就是这些机器如何协同工作&#xff0c;即如何调…

Struts2第一个工程helloStruts极其基本配置

前面已经准备好了Struts-2.3.15&#xff0c;现在就可以直接搭建Struts2的工程了。前面http://blog.csdn.net/huangchnegdada/article/details/9179041有对Struts-2.3.15的准备工作的详述。 首先打开MyEclispe新建一个Web Project&#xff0c;名字就叫Struts2_0100_Introduction…

后台系统可扩展性学习笔记(六)反向代理

文章目录Web代理服务反向代理反向代理作用Web代理服务 Web 代理服务指的是在客户端资源请求和提供这些资源的 Web 服务之间充当中介的角色&#xff0c;代理服务可以实现在客户端&#xff0c;或者从客户端到目标服务器中间的任意环节。 例如&#xff0c;客户端不直接向提供目标…

实验:sigsuspend(),sigprocmask()

实验&#xff1a;sigsuspend(),sigprocmask()源代码&#xff1a;/* * Program: pause_suspend.c * To test the difference between sigsuspend() and paus(). * Author: zsl * Date: 2014-10-17 * First release. * 参见网页&#xff1a;http://blog.csdn.net/liwentao1091/ar…

后台系统可扩展性学习笔记(七)Service Discovery与微服务

文章目录应用层微服务架构服务注册查询 Service Discovery客户端 Service DiscoveryDNS-SD DNS-based Service Discovery服务端 Service Discovery服务注册与注销自注册模式第三方注册模式总结参考应用层 在简单的 3 层结构中&#xff0c;Web 服务层既要处理请求&#xff0c;又…

很久没写代码了,这(那)几天真是累死了。。。先写一个幻方的程序吧

1 #include <stdio.h>2 #include <stdlib.h>3 #include <windows.h>4 5 #define EVEN_DOUBLE_4 4 //双偶的最基本类型&#xff0c;4阶双偶6 #define SCREEN_SIZE 19 //屏幕显示不变形的最大尺寸&#xff08;主要是因为窗口大小限制&#xff09;7 #defi…

后台系统可扩展性学习笔记(八)Service Mesh

文章目录网络传输可靠性将微服务控制下沉到网络栈&#xff1f;Sidecar从 Sidecar 到 Service MeshService Mesh 部署平台参考网络传输可靠性 从计网的学习过程中我们可以知道数据在网络传输中可能会出现一些异常状况&#xff1a; 数据丢失&#xff1a;数据包可能会到达一个缓…

关于Spring batch的学习之CSV2DB

最近在学习Spring batch相关的内容&#xff0c;网上也有不少Spring Batch相关的知识&#xff0c;不过大多都是使用xml进行配置的。这里是我用注解的方式进行相关的学习心得。 首先我们来看如何将一个文本文件中的内容导入到数据库中。 我们先来看一下我们所需要的环境。我们这里…

后台系统可扩展性学习笔记(九)Database Replication

文章目录数据库扩展一致性问题Replication &#xff08;复制&#xff09;异步复制同步复制半同步复制拓扑结构单主结构多主结构无主结构复制具体措施参考数据库扩展 之前在第一章后台系统可扩展性学习笔记&#xff08;一&#xff09;概要谈到&#xff1a;理论上&#xff0c;有…

python中的sum函数.sum(axis=1)

看起来挺简单的样子&#xff0c;但是在给sum函数中加入参数。sum&#xff08;a&#xff0c;axis0&#xff09;或者是.sum(axis1) 就有点不解了 在我实验以后发现 我们平时用的sum应该是默认的axis0 就是普通的相加 而当加入axis1以后就是将一个矩阵的每一行向量相加 例如&…

后台系统可扩展性学习笔记(十)Database Partitioning

为了提升数据库的处理能力&#xff0c;我们把单库扩展成多库&#xff0c;并通过更新同步机制&#xff08;即Replication&#xff09;来保证多份数据的一致性。然而&#xff0c;在 各种复制方案下&#xff0c;每个数据库都持有一份完整数据&#xff0c;基于全量数据提供增删改查…

基于FPGA的HDTV视频图像灰度直方图统计算法设计

随着HDTV的普及&#xff0c;以LCD-TV为主的高清数字电视逐渐进入蓬勃发展时期。与传统CRT电视不同的是&#xff0c;这些高清数字电视需要较复杂的视频处理电路来驱动&#xff0c;比如&#xff1a;模数转换&#xff08;A/D Converter&#xff09;、去隔行&#xff08;De-interla…

Java Swing 影楼管理系统之登录功能

开头打广告&#xff0c;Java1234.com。 首先&#xff0c;来个效果图。 关键代码 1&#xff0c;界面层 private void Jb_DengLuActionPerformed(java.awt.event.ActionEvent evt) {// TODO add your handling code here:String UserName this.Jb_UserNameTxt.getText();String …

Bdsyn百度手机助手是何物,它是怎样神不知鬼不觉地安装到你的电脑里的?

【电脑软件管理中Bdsyn手机助手的问题】Bdsyn手机助手 is developed by Baidu, Inc. and is used by 10 users of Software Informer. 并不是本人安装的&#xff08;应该是自己自己主动安装的&#xff09;&#xff0c;卸载以后过几天又会出如今软件列表里。百度搜索却无法搜索出…

后台系统可扩展性学习笔记(十二)NoSQL

文章目录NoSQL定义NoSQL种类键值存储文档存储宽列存储图形数据库NoSQL 意味着什么ACID vs. BASESQL or NoSQLNoSQL定义 不同于关系型数据库&#xff0c;NoSQL 数据库&#xff08;也叫非 SQL 或非关系型数据库&#xff09;提供的数据存储、检索机制并不是基于表关系建模的。没有…

后台系统可扩展性学习笔记(十三)缓存

文章目录在哪儿加缓存缓存什么内容缓存原始查库结果缓存数据对象怎么查询缓存结果预留缓存模式直读模式直写模式回写式缓存绕写式缓存提前刷新模式缓存满了如何处理参考读写分离、分库分表、反范式化、采用 NoSQL……如果这些扩展手段全都上了&#xff0c;数据响应依旧越来越慢…