程式設計之記憶體分配問題

2021-05-21 22:04:47 字數 4464 閱讀 1295

1.字元

char str[10],sizeof(str)為10;char *pstr = str,sizeof(pstr)為4; 與

char c=;

等價,前者字串常量的最後由系統加上乙個

』/0』,

也等價char c[10]=,是否需要加

』/0』

,完全根據需要決定,但是由於系統對字串常量自動加乙個』/0』

。因此,人們為了使處理方法一致,便於在程式中作相應的處理,在字元陣列也常常人為的加上乙個

』/0』

;但不等價於

char c=

,但此種寫法是完全合法的

。2.結構體

結構體:

struct s1

;問sizeof(s1)等於多少?聰明的你開始思考了,char佔1個位元組,int佔4個字

節,那麼加起來就應該是5。是這樣嗎?

c6中按預設設定得到的結果為8。

我們來好好琢磨一下sizeof的定義——*sizeof*的結果等於物件或者型別所佔的記憶體位元組數,好吧,那就讓我們來看看s1的記憶體分配情況:

s1 s1 = ;

,觀察s1所在的記憶體,你發現了什麼?

以我的vc6.0為例,s1的位址為0x0012ff78,其資料內容如下:

0012ff78: 61 cc cc cc ff ff ff ff

發現了什麼?怎麼中間夾雜了3個位元組的cc?看看msdn上的說明:

原來如此,這就是傳說中的位元組對齊啊!乙個重要的話題出現了。

為什麼需要位元組對齊?計算機組成原理教導我們這樣有助於加快計算機的取數速度,否則就得多花指令週期了。為此,編譯器缺省會對結構體進行處理讓寬度為2的基本資料型別(short等)都位於能被2整除的位址上,讓寬度為4的基本資料型別(int等)都位於能被4整除的位址上,以此類推。這樣,兩個數中間就

可能需要加入填充位元組,所以整個結構體的sizeof值就增長了。

讓我們交換一下s1中char與int的位置:

struct s2

;看看sizeof(s2)的結果為多少,怎麼還是8?再看看記憶體,原來成員c後面仍然有3個填充位元組,這又是為什麼啊?別著急,下面總結規律。

位元組對齊的細節和編譯器實現相關,但一般而言,滿足三個準則:

1) 結構體變數的首位址能夠被其最寬基本型別成員的大小所整除;

2) 結構體每個成員相對於結構體首位址的偏移量(offset)都是成員大小的

整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding);

3) 結構體的總大小為結構體最寬基本型別成員大小的整數倍,如有需要編譯器會在最末乙個成員之後加上填充位元組(trailing padding)。

對於上面的準則,有幾點需要說明:

1) 前面不是說結構體成員的位址是其大小的整數倍,怎麼又說到偏移量了

呢?因為有了第1點存在,所以我們就可以只考慮成員的偏移量,這樣思考起來簡單。想想為什麼。

結構體某個成員相對於*結構*體首位址的偏移量可以通過巨集offsetof()來獲得,這個巨集也在stddef.h中定義,如下:

#define offsetof(s,m) (size_t)&(((s *)0)->m)

例如,想要獲得s2中c的偏移量,方法為

size_t pos = offsetof(s2, c);// pos等於4

2) 基本型別是指前面提到的像char、short、int、float、double這樣的內建資料型別,這裡所說的「資料寬度」就是指其sizeof的大小。由於結構體的成員可以是復合型別,比如另外乙個結構體,所以在尋找最寬基本型別成員時,應當包括復合型別成員的子成員,而不是把復合成員看成是乙個整體。但在確定復合型別成員的偏移位置時則是將復合型別作為整體看待。

這裡敘述起來有點拗口,思考起來也有點撓頭,還是讓我們看看例子吧(具體數值仍以vc6為例,以後不再說明):

struct s3;s1

的最寬簡單成員的型別為int,

s3在考慮最寬簡單型別成員時是將s1「打散」看的

,所以s3的最寬簡單型別為int,這樣,通過s3定義的變數,其儲存空間首位址需要被4整除,整個sizeof(s3)的值也應該被4整除。c1的偏移量為0,s的偏移量呢?這時s是乙個整體,它作為結構體變數也滿足前面三個準則,所以其大小為8,偏移量為4,c1與s之間便需要3個填充位元組,而c2與s之間就不需要了,所以c2的偏移量為12,算上c2的大小為13,13是不能被4整除的,這樣末尾還得補上3個填充位元組。最後得到*sizeof*(s3)的值為16。

通過上面的敘述,我們可以得到乙個公式:

結構體的大小等於最後乙個成員的偏移量加上其大小再加上末尾的填充位元組數

目,即:

*sizeof*( struct ) = offsetof( last item ) + *sizeof*( last item ) +

*sizeof*( trail

ing padding )

到這裡,朋友們應該對*結構*體的*sizeof*有了乙個全新的認識,但不要高興得太早,有乙個影響sizeof的重要參量還未被提及,那便是編譯器的pack指令。它是用來調整結構體對齊方式的,不同編譯器名稱和用法略有不同,vc6中通過#pragma pack實現,也可以直接修改/zp編譯開關。#pragma pack的基本用法為:#pragma pack( n ),n為位元組對齊

數,其取值為1、2、4、8、16,預設是8,如果這個值比結構體成員的sizeof值小,那麼該成員的偏移量應該以此值為準,即是說,結構體成員的偏移量應該取二者的最小值,公式如下:

offsetof( item ) = min( n, *sizeof*( item ) )

再看示例:

#pragma pack(push) // 

將當前pack設定壓棧儲存

#pragma pack(2)// 

必須在*結構*體定義之前使用

struct s1

;struct s3

;#pragma pack(pop) // 

恢復先前的pack設定

計算*sizeof(s1)時,min(2, sizeof(i))的值為2,所以i的偏移量為2,加上

sizeof(i)

等於6,能夠被2整除,所以整個s1的大小為6。

同樣,對於sizeof(s3),s的偏移量為2,c2的偏移量為8,加上sizeof(c2)等於9,不能被2整除,新增乙個填充位元組,所以sizeof(s3)等於10。

現在,朋友們可以輕鬆的出一口氣了,還有一點要注意,「空結構體」(不含資料成員)的大小不為0,而是1。試想乙個「不佔空間」的變數如何被取位址、兩個不同的「空結構體」變數又如何得以區分呢?於是,「空結構體」變數也得被儲存,這樣編譯器也就只能為其分配乙個位元組的空間用於

佔位了。

如下:struct s5 ;

*sizeof*( s5 ); // 

結果為1

8. 含位域*結構*體的

*sizeof*

前面已經說過,位域成員不能單獨被取sizeof值,我們這裡要討論的是含有位域的結構體sizeof,只是考慮到其特殊性而將其專門列了出來。c99規定int、unsigned int*和bool可以作為位域型別,但編譯器幾乎都對此作了

擴充套件,允許它型別型別的存在。

使用位域的主要目的是壓縮儲存,其大致規則為:

1) 如果相鄰位域字段的型別相同,且其位寬之和小於型別的sizeof大小,則

後面的字段將緊鄰前乙個字段儲存,直到不能容納為止;

2) 如果相鄰位域字段的型別相同,但其位寬之和大於型別的sizeof大小,則

後面的字段將從新的儲存單元開始,其偏移量為其型別大小的整數倍;

3) 如果相鄰的位域字段的型別不同,則各編譯器的具體實現有差異,vc6採取不壓縮方式,dev-c++採取壓縮方式;

4) 如果位域字段之間穿插著非位域字段,則不進行壓縮;

5) 整個結構體的總大小為最寬基本型別成員大小的整數倍。

還是讓我們來看看例子。

示例1:

struct bf1

;其記憶體布局為:

|_f1__|__f2__|_|____f3___|____|

|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|

0 3   7 8   1316

位域型別為char,第1個位元組僅能容納下f1和f2,所以f2被壓縮到第1個位元組中,而f3只能從下乙個位元組開始。因此*sizeof*(bf1)的結果為2。

示例2:

struct bf2

;由於相鄰位域型別不同,在vc6中其sizeof為6,在dev-c++中為2。

示例3:

struct bf3

;非位域字段穿插在其中,不會產生壓縮,在vc6和dev-c++中得到的大小均為3。

9. 聯合體的

*sizeof*

*結構*體在記憶體組織上是順序式的,聯合體則是重疊式,各成員共享一段記憶體,所以整個聯合體的*sizeof*也就是每個成員*sizeof*的最大值。*結構*體的成員也可以是復合型別,這裡,復合型別成員是被作為整體考慮的。

所以,下面例子中,u的*sizeof*值*等*於*sizeof*(s)。

union u;

閒談程式設計之 記憶體

記憶體是對一堆電晶體的連續抽象 別告訴我你不知道電晶體,也別說你不知道他的工作原理,否則請看 每8個電晶體組成乙個位元組,每個位元組都有自己的位址,這個位址就是傳說中的記憶體位址,如果你在c中寫下如下的表示式 以下在未經特殊說明的情況下,均為c語言環境 a 0 a是乙個符號,它有自己的真實位址,如果...

C C 程式設計之記憶體管理

1 從 全域性 靜態儲存區域分配 內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在,例如,全域性變數,靜態變數。2 常量儲存區 儲存程式中的常量。3 棧區 在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束後這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器...

C C 程式設計之記憶體管理

記憶體分配方式 c語言中記憶體分配方式 1 從 全域性 靜態儲存區域分配 內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在,例如,全域性變數,靜態變數。2 常量儲存區 儲存程式中的常量。3 棧區 在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束後這些儲存單元...