堆、棧與大小端存儲


前言

首先先提一個和操作系統主引導相關的概念:一個有效的主引導扇區,其起始地址為0X7c00,最后兩個字節的數據必須是0x55、0xaa;否則這個扇區里保存的就不是一些有意而為的數據。這三個數都是所謂的"魔數",而0x7c00的由來是這樣的:雖然一個Mbr大小為512字節,但是加上堆棧區大小應該為1KB左右。而在8086CPU中0x0~0x3ff是存放中斷向量表的,按照DOS1.0的32KB標准,我們最好將其放置在末尾處,大小占1KB,所以起始位置為31KB處,即0x7c00.然后再看0x55與0xaa,如果是兩個單獨的字節,那么低地址放0x55,高地址放0xaa;但是如果是一個字單位,呢就應該是0xaa55,這里涉及到了大小端字節序。

但是自己突然又有個問題,這里的存放順序是大小端問題,但是數據存儲又分為存在堆與棧中,這兩者是怎么聯系起來的?自己以前寫過關於結構體內存存放的一篇文章,現在看來當時還沒有想到與堆棧之間的聯系,那現在來看一下數據存放與堆棧之間的關系。

大小端存儲

這個概念在網絡編程中應該會涉及到,雖然現在各種服務器模型和框架(事實上在設計socket網絡編程之初就已經考慮了這個問題)都已經有了非常好的處理大小端存儲的方式,htonl、ntohl....;但是了解一下為什么這么做也是比較重要的:

image.png

簡單來說,從低地址往高地址(一般visual studio提供的內存查看方式都是這樣,我們就采用這種描述方式)來看,小端會將數據的低字節部分先放入,然后放高字節部分,就比如int x=0x12345678,存放在內存中就是78 56 34 12。大端則和我們讀寫方式一致(從低到高地址的讀寫方式),同樣的x存放順序為12 34 56 78.

堆與棧

首先看一下堆與棧的結構:

image.png

堆與棧其實是在同一段地址空間的,不同的是棧是往下增長(8086CPU中),堆是往上增長;所以說應該寫道堆的數據因為太多寫到棧中是合理的(雖然不應該這么做),但是因為棧是程序自動分配、堆是自己分配的,所以非常有可能會發生堆棧沖突(從堆中分配內存失敗或者爆棧)!

在C++中,內存分成5個區,分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。

棧:就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。里面的變量通常是局部變量、函數參數等。

堆:就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。

自由存儲區:就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。

全局/靜態存儲區:全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分了,他們共同占用同一塊內存區。,在實現上都會區別對待初始化的和沒初始化的

常量存儲區:這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改。

對於堆區、棧區和靜態存儲區它們之間最大的不同在於,棧的生命周期很短暫。但是堆區和靜態存儲區的生命周期相當於與程序的生命同時存在(如果不在程序運行中間將堆內存delete的話),我們將這種變量或數據成為全局變量或數據。但是,對於堆區的內存空間使用更加靈活,因為它允許你在不需要它的時候,隨時將它釋放掉,而靜態存儲區將一直存在於程序的整個生命周期中。

聯系

我們分別寫一個非常簡單的程序來看一下0x12345678a這兩個是怎么存儲在內存中的:

棧中:

TIM截圖20181202135611.png

堆中:

image.png

靜態存儲區中:

TIM截圖20181202142501.png

但是自己一直覺得有問題的地方是這樣的,我們都知道程序在“中斷”跳轉的時候是會在棧中保存程序的上下文的(返回地址、變量.....);所以我認為我們在保存結構體的時候,雖然結構體也是在編譯的時候視為數據類型,然后分配在棧中。但是結構體中的成員變量的存放應該也和自身的類型有關,所以如果他們也是局部變量的時候應該也分配在棧中,其地址順序也應該符合棧的存放方式,但是看下圖:

image.png

順序不對啊?其實這里犯了一個很大的錯誤,就是並沒有意識到成員變量此時並不是所謂的"局部變量"了!!而是非靜態成員變量,我們可以通過類的內存分配看到如下結構:

image.png

這里的x與c都是非靜態成員變量,是分配在堆中的(經友人指出,並不是因為非靜態成員變量存放在堆中,因為非靜態成員變量可能位於堆中也可能位於棧中,其存儲位置與類的實例的位置是一致的,具體原因是因為“Non-static data members of a (non-union) class with the same access control are allocated so
that later members have higher addresses within a class object”。而自己在看很多博客中都提到非靜態成員變量是位於堆的,而這個又和自己的實驗結果相符,所以之前的除了錯誤的結論,這說明辨別知識的真偽還需要自己的判斷........
),所以可以解釋了。

而成員變量和局部變量是有區別的:
a定義的位置不同
成員變量:定義於類中,作用於整個類
局部變量:定義於方法或者語句中,作用於該方法或者該語句。
b內存中出現的時間和位置不同
成員變量:當對象創建時,出現在堆內存當中。
局部變量:所屬區域被運算時,出現在棧內存當中。
c生命周期不同
成員變量:隨着對象的出現而出現,隨着對象的消失而消失。
局部變量:隨着所屬區域運算結束,它就被釋放。
d初始化值不同
成員變量:成員變量因為在堆內存中,所以它有默認初始值。
局部變量:沒有默認的初始值。

那么如果我們在class中定義一個函數,函數中用了局部變量,那么這個類中非靜態成員函數的局部變量是符合我們說的數據存放規則的么?答案是:是的!

image.png

這里引用一下總結的靜態成員變量與非靜態成員變量的區別:

1、從保存位置:
a) 靜態成員變量: 方法區的靜態區域
b) 非靜態成員變量: (**與類的實例化方式有關**)

2、從書寫格式上看:
a) 靜態成員變量: 在數據類型前面多了一個static修飾
b) 非靜態成員變量: 沒有static修飾

3、從生命周期上看:
a) 靜態成員變量:  在類加載的時候,類加載完成,就分配完空間;直到類被卸載時空間被回收
b) 非靜態成員變量: 創建對象的時候分配空間; 對象變為垃圾空間被回收的時候被銷毀

4、從使用方法上看:
a) 靜態成員變量:  直接通過類名使用
b) 非靜態成員變量: 必須通過對象使用

5、從修改后的影響范圍上看:
a) 靜態成員變量: 對該類的所有對象都有影響;
b) 非靜態成員變量:  只對一個對象有影響

結語

其實寫完以后發現其實並不需要糾結的,就是根據變量類型按照堆或棧的順序存儲,然后再根據數據大小分成字節,再按字節序存儲就行了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM