類所占內(nèi)存:
類所占內(nèi)存的大小是由成員變量(靜態(tài)變量除外)決定的,成員函數(shù)(這是笼統(tǒng)的說(shuō),后面會(huì)細(xì)說(shuō))是不計(jì)算在內(nèi)的。
摘抄部分:
成員函數(shù)還是以一般的函數(shù)一樣的存在。a.fun()是通過fun(a.this)來(lái)調(diào)用的。所謂成員函數(shù)只是在名義上是類里的。其實(shí)成員函數(shù)的大小不在類的對(duì)象里面,同一個(gè)類的多個(gè)對(duì)象共享函數(shù)代碼。而我們?cè)L問類的成員函數(shù)是通過類里面的一個(gè)指針實(shí)現(xiàn),而這個(gè)指針指向的是一個(gè)table,table里面記錄的各個(gè)成員函數(shù)的地址(當(dāng)然不同的編譯可能略有不同的實(shí)現(xiàn))。所以我們?cè)L問成員函數(shù)是間接獲得地址的。所以這樣也就增加了一定的時(shí)間開銷,這也就是為什么我們提倡把一些簡(jiǎn)短的,調(diào)用頻率高的函數(shù)聲明為inline形式(內(nèi)聯(lián)函數(shù))。
(一)class CBase { }; sizeof(CBase)=1;
為什么空的什么都沒有是1呢?c++要求每個(gè)實(shí)例在內(nèi)存中都有獨(dú)一無(wú)二的地址。//注意這句話!!!!!!!!!!空類也會(huì)被實(shí)例化,所以編譯器會(huì)給空類隱含的添加一個(gè)字節(jié),這樣空類實(shí)例化之后就有了獨(dú)一無(wú)二的地址了。所以空類的sizeof為1。
(二)
class CBase { int a; char p; }; sizeof(CBase)=8;記得對(duì)齊的問題。int 占4字節(jié)//注意這點(diǎn)和struct的對(duì)齊原則很像!!!!!char占一字節(jié),補(bǔ)齊3字節(jié)
(三)class CBase { public: CBase(void); virtual ~CBase(void); PRivate: int a; char *p; }; 再運(yùn)行:sizeof(CBase)=12
C++ 類中有虛函數(shù)的時(shí)候有一個(gè)指向虛函數(shù)的指針(vptr),在32位系統(tǒng)分配指針大小為4字節(jié)。無(wú)論多少個(gè)虛函數(shù),只有這一個(gè)指針,4字節(jié)。//注意一般的函數(shù)是沒有這個(gè)指針的,而且也不占類的內(nèi)存。
(四)class CChild : public CBase { public: CChild(void); ~CChild(void);
virtual void test();private: int b; }; 輸出:sizeof(CChild)=16;可見子類的大小是本身成員變量的大小加上父類的大小。//其中有一部分是虛擬函數(shù)表的原因,一定要知道
父類子類共享一個(gè)虛函數(shù)指針
(五)
#include<iostream.h>
class a {};
class b{};
class c:public a{
virtual void fun()=0;
};
class d:public b,public c{};
int main()
{
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
return 0;}
程序執(zhí)行的輸出結(jié)果為:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
前三種情況比較常見,注意第四種情況。類d的大小更讓初學(xué)者疑惑吧,類d是由類b,c派生邇來(lái)的,它的大小應(yīng)該為二者之和5,為什么卻是8 呢?這是因?yàn)闉榱颂岣邔?shí)例在內(nèi)存中的存取效率.類的大小往往被調(diào)整到系統(tǒng)的整數(shù)倍.并采取就近的法則,里哪個(gè)最近的倍數(shù),就是該類的大小,所以類d的大小為8個(gè)字節(jié).
總結(jié):
空的類是會(huì)占用內(nèi)存空間的,而且大小是1,原因是C++要求每個(gè)實(shí)例在內(nèi)存中都有獨(dú)一無(wú)二的地址。
(一)類內(nèi)部的成員變量:
普通的變量:是要占用內(nèi)存的,但是要注意對(duì)齊原則(這點(diǎn)和struct類型很相似)。static修飾的靜態(tài)變量:不占用內(nèi)容,原因是編譯器將其放在全局變量區(qū)。(二)類內(nèi)部的成員函數(shù):
普通函數(shù):不占用內(nèi)存。虛函數(shù):要占用4個(gè)字節(jié),用來(lái)指定虛函數(shù)的虛擬函數(shù)表的入口地址。所以一個(gè)類的虛函數(shù)所占用的地址是不變的,和虛函數(shù)的個(gè)數(shù)是沒有關(guān)系的結(jié)構(gòu)體所占內(nèi)存:結(jié)構(gòu)體存在內(nèi)存對(duì)齊,類(對(duì)象)也如此,甚至于所有變量在內(nèi)存中的存儲(chǔ)也有對(duì)齊一說(shuō)(只是這些對(duì)程序員是透明的,不需要關(guān)心)。實(shí)際上,這種對(duì)齊是為了在空間與復(fù)雜度上達(dá)到平衡的一種技術(shù)手段,簡(jiǎn)單的講,是為了在可接受的空間浪費(fèi)的前提下,盡可能的提高對(duì)相同運(yùn)算過程的最少(快)處理。先舉個(gè)例子:
假設(shè)機(jī)器字長(zhǎng)是32位的(即4字節(jié),下面示例均按此字長(zhǎng)),也就是說(shuō)處理任何內(nèi)存中的數(shù)據(jù),其實(shí)都是按32位的單位進(jìn)行的。現(xiàn)在有2個(gè)變量:
char A; int B;假設(shè)這2個(gè)變量是從內(nèi)存地址0開始分配的,如果不考慮對(duì)齊,應(yīng)該是這樣存儲(chǔ)的(見下圖,以intel上的little endian為例,為了形象,每16個(gè)字節(jié)分做一行,后同):

因?yàn)橛?jì)算機(jī)的字長(zhǎng)是4字節(jié)的,所以在處理變量A與B時(shí)的過程可能大致為:
A:將0x00-0x03共32位讀入寄存器,再通過左移24位再右移24位運(yùn)算得到a的值(或與0x000000FF做與運(yùn)算)
B:將0x00-0x03這32位讀入寄存器,通過位運(yùn)算得到低24位的值;再將0x04-0x07這32位讀入寄存器,通過位運(yùn)算得到高8位的值;再與最先得到的24位做位運(yùn)算,才可得到整個(gè)32位的值。
上面敘述可知,對(duì)a的處理是最簡(jiǎn)處理,可對(duì)b的處理,本身是個(gè)32位數(shù),處理的時(shí)候卻得折成2部分,之后再合并,效率上就有些低了。
想解決這個(gè)問題,就需要付出幾個(gè)字節(jié)浪費(fèi)的代價(jià),改為下圖的分配方式:

按上面的分配方式,A的處理過程不變;B卻簡(jiǎn)單得多了:只需將0x04-0x07這32位讀入寄存器就OK了。
我們可以具體談結(jié)構(gòu)體或類成員的對(duì)齊了:
結(jié)構(gòu)體在編譯成機(jī)器代碼后,其實(shí)就沒有本身的集合概念了,而類,實(shí)際上是個(gè)加強(qiáng)版的結(jié)構(gòu)體,類的對(duì)象在實(shí)例化時(shí),內(nèi)存中申請(qǐng)的就是一些變量的空間集合(類似于結(jié)構(gòu)體,同時(shí)也不包含函數(shù)指針)。這些集合中的每個(gè)變量,在使用中,都需要涉及上述的加工原則,自然也就需要在效率與空間之間做出權(quán)衡。
為了便捷加工連續(xù)多個(gè)相同類型原始變量,同時(shí)簡(jiǎn)化原始變量尋址,再匯總上述最少處理原則,通常可以將原始變量的長(zhǎng)度做為針對(duì)此變量的分配單位,比如內(nèi)存可用64個(gè)單元,如果某原始變量長(zhǎng)度為8字節(jié),即使機(jī)器字長(zhǎng)為4字節(jié),分配的時(shí)候也以8字節(jié)對(duì)齊(看似IO次數(shù)是相同的),這樣,尋址、分配時(shí),均可以按每8字節(jié)為單位進(jìn)行,簡(jiǎn)化了操作,也可以更高效。
系統(tǒng)默認(rèn)的對(duì)齊規(guī)則,追求的至少兩點(diǎn):1、變量的最高效加工 2、達(dá)到目的1的最少空間
舉個(gè)例子,一個(gè)結(jié)構(gòu)體如下:
//by www.datahf.net zhangyutypedef struct T{ char c; //本身長(zhǎng)度1字節(jié) __int64 d; //本身長(zhǎng)度8字節(jié) int e; //本身長(zhǎng)度4字節(jié) short f; //本身長(zhǎng)度2字節(jié) char g; //本身長(zhǎng)度1字節(jié) short h; //本身長(zhǎng)度2字節(jié)};假設(shè)定義了一個(gè)結(jié)構(gòu)體變量C,在內(nèi)存中分配到了0x00的位置,顯然:
對(duì)于成員C.c 無(wú)論如何,也是一次寄存器讀入,所以先占一個(gè)字節(jié)。
對(duì)于成員C.d 是個(gè)64位的變量,如果緊跟著C.c存儲(chǔ),則讀入寄存器至少需要3次,為了實(shí)現(xiàn)最少的2次讀入,至少需要以4字節(jié)對(duì)齊;同時(shí)對(duì)于8字節(jié)的原始變量,為了在尋址單位上統(tǒng)一,則需要按8字節(jié)對(duì)齊,所以,應(yīng)該分配到0x08-0xF的位置。
對(duì)于成員C.e 是個(gè)32位的變量,自然只需滿足分配起始為整數(shù)個(gè)32位即可,所以分配至0x10-0x13。
對(duì)于成員C.f 是個(gè)16位的變量,直接分配在0x14-0x16上,這樣,反正只需一次讀入寄存器后加工,邊界也與16位對(duì)齊。
對(duì)于成員C.g 是個(gè)8位的變量,本身也得一次讀入寄存器后加工,同時(shí)對(duì)于1個(gè)字節(jié)的變量,存儲(chǔ)在任何字節(jié)開始都是對(duì)齊,所以,分配到0x17的位置。
對(duì)于成員C.h 是個(gè)16位的變量,為了保證與16位邊界對(duì)齊,所以,分配到0x18-0x1A的位置。
分配圖如下(還不正確,耐心讀下去):

結(jié)構(gòu)體C的占用空間到h結(jié)束就可以了嗎?我們找個(gè)示例:如果定義一個(gè)結(jié)構(gòu)體數(shù)組 CA[2],按變量分配的原則,這2個(gè)結(jié)構(gòu)體應(yīng)該是在內(nèi)存中連續(xù)存儲(chǔ)的,分配應(yīng)該如下圖:

分析一下上圖,明顯可知,CA[1]的很多成員都不再對(duì)齊了,究其原因,是結(jié)構(gòu)體的開始邊界不對(duì)齊。
那結(jié)構(gòu)體的開始偏移滿足什么條件才可以使其成員全部對(duì)齊呢。想一想就明白了:很簡(jiǎn)單,保證結(jié)構(gòu)體長(zhǎng)度是原始成員最長(zhǎng)分配的整數(shù)倍即可。 上述結(jié)構(gòu)體應(yīng)該按最長(zhǎng)的.d成員對(duì)齊,即與8字節(jié)對(duì)齊,這樣正確的分配圖如下:

當(dāng)然結(jié)構(gòu)體T的長(zhǎng)度:sizeof(T)==0x20;
再舉個(gè)例子,看看在默認(rèn)對(duì)齊規(guī)則下,各結(jié)構(gòu)體成員的對(duì)齊規(guī)則:
//by www.datahf.net zhangyutypedef struct A { char c; //1個(gè)字節(jié) int d; //4個(gè)字節(jié),要與4字節(jié)對(duì)齊,所以分配至第4個(gè)字節(jié)處 short e; //2個(gè)字節(jié), 上述兩個(gè)成員過后,本身就是與2對(duì)齊的,所以之前無(wú)填充 }; //整個(gè)結(jié)構(gòu)體,最長(zhǎng)的成員為4個(gè)字節(jié),需要總長(zhǎng)度與4字節(jié)對(duì)齊,所以, sizeof(A)==12 typedef struct B { char c; //1個(gè)字節(jié) __int64 d; //8個(gè)字節(jié),位置要與8字節(jié)對(duì)齊,所以分配到第8個(gè)字節(jié)處 int e; //4個(gè)字節(jié),成員d結(jié)束于15字節(jié),緊跟的16字節(jié)對(duì)齊于4字節(jié),所以分配到16-19 short f; //2個(gè)字節(jié),成員e結(jié)束于19字節(jié),緊跟的20字節(jié)對(duì)齊于2字節(jié),所以分配到20-21 A g; //結(jié)構(gòu)體長(zhǎng)為12字節(jié),最長(zhǎng)成員為4字節(jié),需按4字節(jié)對(duì)齊,所以前面跳過2個(gè)字節(jié),//到24-35字節(jié)處 char h; //1個(gè)字節(jié),分配到36字節(jié)處 int i; //4個(gè)字節(jié),要對(duì)齊4字節(jié),跳過3字節(jié),分配到40-43 字節(jié)}; //整個(gè)結(jié)構(gòu)體的最大分配成員為8字節(jié),所以結(jié)構(gòu)體后面加5字節(jié)填充,被到48字節(jié)。故://sizeof(B)==48;具體的分配圖如下:

上述全部測(cè)試代碼如下:
//by www.datahf.net zhangyu#include "stdio.h" typedef struct A { char c; int d; short e; }; typedef struct B { char c; __int64 d; int e; short f; A g; char h; int i; }; typedef struct C { char c; __int64 d; int e; short f; char g; short h; }; typedef struct D { char a; short b; char c; }; int main() { B *b=new B; void *s[32]; s[0]=b; s[1]=&b->c; s[2]=&b->d; s[3]=&b->e; s[4]=&b->f; s[5]=&b->g; s[6]=&b->h; s[7]=&b->g.c; s[8]=&b->g.d; s[9]=&b->g.e; s[10]=&b->i; b->c= 0x11; b->d= 0x2222222222222222; b->e= 0x33333333; b->f=0x4444; b->g.c=0x50; b->g.d=0x51515151; b->g.e=0x5252; b->h=0x66; int i1=sizeof(A); int i2=sizeof(B); int i3=sizeof(C); int i4=sizeof(D); printf("i1:%d/ni2:%d/ni3:%d/ni4:%d/n",i1,i2,i3,i4);//12 48 32 6 }
運(yùn)行時(shí)的內(nèi)存情況如下圖:

最后,簡(jiǎn)單加工一下轉(zhuǎn)載過來(lái)的內(nèi)存對(duì)齊正式原則:
先介紹四個(gè)概念:
1)數(shù)據(jù)類型自身的對(duì)齊值:基本數(shù)據(jù)類型的自身對(duì)齊值,等于sizeof(基本數(shù)據(jù)類型)。
2)指定對(duì)齊值:#pragma pack (value)時(shí)的指定對(duì)齊值value。
3)結(jié)構(gòu)體或者類的自身對(duì)齊值:其成員中自身對(duì)齊值最大的那個(gè)值。
4)數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對(duì)齊值:自身對(duì)齊值和指定對(duì)齊值中較小的那個(gè)值。
有效對(duì)齊值N是最終用來(lái)決定數(shù)據(jù)存放地址方式的值,最重要。有效對(duì)齊N,就是表示“對(duì)齊在N上”,也就是說(shuō)該數(shù)據(jù)的"存放起始地址%N=0".而數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)變量都是按定義的先后順序來(lái)排放的。第一個(gè)數(shù)據(jù)變量的起始地址就是 數(shù)據(jù)結(jié)構(gòu)的起始地址。結(jié)構(gòu)體的成員變量要對(duì)齊排放,結(jié)構(gòu)體本身也要根據(jù)自身的有效對(duì)齊值圓整(就是結(jié)構(gòu)體成員變量占用總長(zhǎng)度需要是對(duì)結(jié)構(gòu)體有效對(duì)齊值的整 數(shù)倍)
#pragma pack (value)來(lái)告訴編譯器,使用我們指定的對(duì)齊值來(lái)取代缺省的。
如#pragma pack (1) /*指定按2字節(jié)對(duì)齊*/
#pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注