探討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.pswp.cn/news/376595.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/376595.shtml
英文地址,請注明出處:http://en.pswp.cn/news/376595.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

后臺系統可擴展性學習筆記(一)概要

文章目錄系統大致架構可擴展性負載均衡器與會話保持引入冗余增強系統可用性緩存減輕數據庫壓力異步處理參考系統大致架構 當一個用戶請求從客戶端出發&#xff0c;經過網絡傳輸&#xff0c;達到 Web 服務層&#xff0c;接著進入應用層&#xff0c;最后抵達數據層&#xff0c;它…

poj 3728(LCA + dp)

題目鏈接&#xff1a;http://poj.org/problem?id3728 思路&#xff1a;題目的意思是求樹上a -> b的路徑上的最大收益&#xff08;在最小值買入&#xff0c;在最大值賣出&#xff09;。 我們假設路徑a - > b 之間的LCA(a, b) f, 并且另up[a]表示a - > f之間的最大收益…

成功之路

1、每天都要有進步&#xff0c;都要有新知識的收獲。 2、工作認真負責&#xff0c;高效的完成&#xff0c;多總結。 3、自己多練習一些感興趣的東西&#xff0c;實踐&#xff01;&#xff01;&#xff01; 4、寫博客。 5、百度、騰訊、阿里是目標&#xff0c;差距還很大&#x…

后臺系統可擴展性學習筆記(二)權衡取舍

文章目錄性能與可擴展性延遲與吞吐量可用性與一致性一致性模式可用性模式可用性衡量參考系統設計中也面臨許多權衡取舍&#xff1a;性能與可擴展性延遲與吞吐量可用性與一致性 性能與可擴展性 可擴展&#xff0c;意味著服務能以加資源的方式成比例地提升性能&#xff0c;性能…

iOS中使用子線程的完整方法

第一步&#xff1a;開啟子線程 //開啟子線程到網絡上獲取數據myFirstThread [[NSThread alloc]initWithTarget:self selector:selector(thread1GetData) object:nil];[myFirstThread setName:"第一個子線程,用于獲取網絡數據"];[myFirstThread start]; 第二步&…

DIV的表單布局

表單布局其實用表格最好了&#xff0c;可是表格的話&#xff0c;無法定位&#xff0c;這個是一個硬傷。 <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>表單布局</title> <link rel"stylesheet" …

后臺系統可擴展性學習筆記(三)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…

Javascript 基礎—變量 運算符

經過找工作筆試的洗禮&#xff0c;感覺自己js語法方面掌握的不是很系統&#xff0c;今天來梳理下——變量以及運算符。 基礎篇 和C語言的不同點&#xff1a;是一種弱類型語言&#xff0c;申明變量時不需要指定類型&#xff1b;變量名的命名方法也有不同&#xff1b;簡單類型種類…

后臺系統可擴展性學習筆記(五)負載均衡

文章目錄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…

[LeetCode]Find Minimum in Rotated Sorted Array

題目描述&#xff1a; Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2). Find the minimum element. You may assume no duplicate exists in the array. 解題方案&#xff1a; 直接貼代碼&…

后臺系統可擴展性學習筆記(六)反向代理

文章目錄Web代理服務反向代理反向代理作用Web代理服務 Web 代理服務指的是在客戶端資源請求和提供這些資源的 Web 服務之間充當中介的角色&#xff0c;代理服務可以實現在客戶端&#xff0c;或者從客戶端到目標服務器中間的任意環節。 例如&#xff0c;客戶端不直接向提供目標…

(C)單鏈表

老師版 1 #include <stdio.h>2 #include <stdlib.h>3 4 // 定于Node數據類型5 struct Node6 {7 int data; // 數據域8 struct Node *next; // 指針域9 };10 11 // 創建一個單鏈表&#xff0c;并把head節點返回&#xff1b;…

實驗: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…

#pragma once

http://baike.baidu.com/view/1276747.htm?fraladdin 轉載于:https://www.cnblogs.com/prayer521/p/4069040.html

后臺系統可擴展性學習筆記(八)Service Mesh

文章目錄網絡傳輸可靠性將微服務控制下沉到網絡棧&#xff1f;Sidecar從 Sidecar 到 Service MeshService Mesh 部署平臺參考網絡傳輸可靠性 從計網的學習過程中我們可以知道數據在網絡傳輸中可能會出現一些異常狀況&#xff1a; 數據丟失&#xff1a;數據包可能會到達一個緩…