一、前言
編譯環境是vs2010(32位)。
- <span style="font-size:18px;">#include<iostream>
- #include<stdio.h>
- #include<string.h>
- using namespace std;
- typedef struct
- {
- int a;
- char b;
- }A_t;
- typedef struct
- {
- int a;
- char b;
- char c;
- }B_t;
- typedef struct
- {
- char a;
- int b;
- char c;
- }C_t;
- void main()
- {
- char*a=0;
- cout<<sizeof(<span style="color:#ff0000;">a</span>)<<endl;//4
- cout<<sizeof(<span style="color:#ff0000;">*a</span>)<<endl;//1--這個能理解
- cout<<sizeof(<span style="color:#ff0000;">A_t</span>)<<endl;//8
- cout<<sizeof(<span style="color:#ff0000;">B_t</span>)<<endl;//8
- cout<<sizeof<span style="color:#ff0000;">(C_t</span>)<<endl;//12
- }</span>
爲什麼是這樣的結果啊?
二、語法
sizeof有三種語法形式,如下:
1) sizeof( object ); // sizeof( 對象 );
2) sizeof( type_name ); // sizeof( 類型 );
3) sizeof object; // sizeof 對象;
三. 指針變量的sizeof
既然是來存放地址的,那麼它當然等於計算機內部地址總線的寬度。所以在32位計算機中,一個指針變量的返回值必定是4(以字節爲單位),在64位系統中指針變量的sizeof結果爲8。
- <span style="font-size:18px;">#include<iostream>
- #include<stdio.h>
- #include<string.h>
- using namespace std;
- int main()
- {
- char *a=0;
- char* pc = "abc";
- int* pi;
- string* ps;
- char** ppc = &pc;
- <span style="color:#ff0000;">void (*pf)();// 函數指針</span>
- cout<<sizeof(<span style="color:#ff0000;">char</span>)<<endl; //1
- cout<<sizeof(<span style="color:#ff0000;">a</span>)<<endl;//4
- cout<<sizeof(<span style="color:#ff0000;">*a</span>)<<endl;//1
- cout<<sizeof(<span style="color:#ff0000;">pc</span>)<<endl; //4(指針)
- cout<<sizeof(<span style="color:#ff0000;">pi</span>)<<endl;//4(指針)
- cout<<sizeof(<span style="color:#ff0000;">ps</span>)<<endl; //4(string型指針)
- cout<<sizeof(<span style="color:#ff0000;">ppc</span>)<<endl; //4(指針)
- cout<<sizeof(<span style="color:#ff0000;">pf</span>)<<endl;//4
- }</span>
指針變量的sizeof值與指針所指的對象沒有任何關係,正是由於所有的指針變量所佔內存
大小相等,所以MFC消息處理函數使用兩個參數WPARAM、LPARAM就能傳遞各種複雜的消息結構(使用指向結構體的指針)。
四.、數組的sizeof
數組的sizeof值等於數組所佔用的內存字節數,如:
- <span style="font-size:18px;">#include<iostream>
- #include<stdio.h>
- #include<string.h>
- using namespace std;
- int main()
- {
- char b1[]="123";
- int b2[3];
- <span style="color:#ff0000;">int c1=sizeof(b1)/sizeof(char);
- int c2=sizeof(b1)/sizeof(b1[0]);
- int c3=sizeof(b2)/sizeof(int);</span>
- int c4=sizeof(b2)/sizeof(b2[0]);
- cout<<sizeof(b1)<<' '<<c1<<' '<<c2<<endl;//4 4 4
- cout<<sizeof(b2)<<' '<<c3<<' '<<c4<<endl;//12(3*4 依賴int) 3 3
- }</span>
1.數組長度
char a1[] = "abc";
int a2[3];
sizeof( a1 ); // 結果爲4,字符串末尾還存在一個NULL終止符
sizeof( a2 ); // 結果爲3*4=12(依賴於int)
2.數組元素個數
int c1 = sizeof( a1 ) / sizeof( char ); // 總長度/單個元素的長度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 總長度/第一個元素的長度
3.數組“傳址”(數組爲函數參數)
我們可以思考一下,下面的c3,c4值應該是多少呢?
- <span style="font-size:18px;">void foo3(char a3[3])
- {
- int c3 = sizeof( a3 ); // c3 ==
- }
- void foo4(char a4[])
- {
- int c4 = sizeof( a4 ); // c4 ==
- } </span>
也許當你試圖回答c4的值時已經意識到c3答錯了,是的,c3!=3。這裏函數參數a3已不再是數組類型,而是蛻變成指針,相當於char* a3,爲什麼?仔細想想就不難明白,我們調用函數foo1時,程序會在棧上分配一個大小爲3的數組嗎?不會!數組是“傳址”的,調用者只需將實參的地址傳遞過去,所以a3自然爲指針類型(char*),c3的值也就爲4。
五. 結構體的sizeof
結構體相對而言最容易碰到而且最容易出錯。讓我們先看一個結構體:
struct S1
{
char c;
int i;
};
編譯得到結果爲8!
我們來好好琢磨一下sizeof的定義——sizeof的結果等於對象或者類型所佔的內存字節數,好吧,那就讓我們來看看S1的內存分配情況:
S1 s1 = { a , 0xFFFFFFFF };
定義上面的變量後,加上斷點,運行程序,觀察s1所在的內存,你發現了什麼?
以我的Vs爲例,s1的地址爲0x0012FF78,其數據內容如下:
0012FF78: 61 CC CC CC FF FF FF FF
發現了什麼?怎麼中間夾雜了3個字節的CC?看看MSDN上的說明:
When applied to a structure type or variable, sizeof returns the actual size,
which may include padding bytes inserted for alignment.
原來如此,這就是傳說中的字節對齊啊!一個重要的話題出現了。
1.怎麼判斷內存對齊規則,sizeof的結果怎麼來的,牢記如下3條規則(在沒有#pragma pack宏的情況下):
(1)數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數組,結構體等)的整數倍開始(比如int在32位機爲4字節,則要從4的整數倍地址開始存儲)。
(2)結構體作爲成員:如果一個結構體裏有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲(struct a 裏存有struct b,b裏有char,int,double等元素,那麼b應該從8的整數倍開始存儲)。
(3)收尾工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊。
類型
對齊方式(變量存放的起始地址相對於結構的起始地址的偏移量)
Char
偏移量必須爲sizeof(char)即1的倍數
int
偏移量必須爲sizeof(int)即4的倍數
float
偏移量必須爲sizeof(float)即4的倍數
double
偏移量必須爲sizeof(double)即8的倍數
Short
偏移量必須爲sizeof(short)即2的倍數
- <span style="font-size:18px;">#include<iostream>
- #include<stdio.h>
- #include<string.h>
- using namespace std;
- typedef struct bb
- {
- int id; //[0]....[3]
- double weight; //[8].....[15] 原則1
- float height; //[16]..[19],總長要爲8的整數倍,補齊[20]...[23] 原則3
- }BB;
- typedef struct aa
- {
- char name[2]; //[0] [1]
- int id; //[4]....[7] 原則1
- double score;// [8]...[15]
- short t; //[16]...[17]
- BB b; //[24]...[47] 原則2、3
- }AA;
- int main()
- {
- cout<<sizeof(BB)<<endl; //爲24
- cout<<sizeof(AA)<<endl; //爲48
- return 0;
- }</span>
2.帶#pragma pack()
在代碼前加上一句#pragma pack(1),bb就是4+8+4=16。Aa就是2+4+8+2+16=32.
六、聯合體的sizeof
結構體在內存組織上是順序式的,聯合體則是重疊式,各成員共享一段內存,所以整個聯合體的sizeof也就是每個成員sizeof的最大值。結構體的成員也可以是複合類型,這裏,複合類型成員是被作爲整體考慮的。
所以,下面例子中,U的sizeof值等於sizeof(s)。
- <span style="font-size:18px;">union U
- {
- int i;
- char c;
- AA s;
- };</span>
七、類的sizeof
1、空類的sizeof是1。空類是指沒有成員的類,類中的函數不佔空間,除非是虛函數。
如:
- <span style="font-size:18px;"> class A
- {
- public:
- A(){}
- ~A(){}
- void fun(){}
- };</span>
sizeof(A)是1.
注:
- <span style="font-size:18px;"> class A1
- {
- public:
- A1(){}
- ~A1(){}
- void fun(){}
- char a[0];
- };</span>
sizeof(A1)也是1.(VC6.0下編譯)
2、若類中包含成員,則類對象的大小隻包括其中非靜態成員經過對齊所佔的空間,對齊方式和結構體相同。如:
- <span style="font-size:18px;">class A
- {
- public:
- int b;
- float c;
- char d;
- };</span>
sizeof(A)是12.
- <span style="font-size:18px;">class A
- {
- public:
- static int a;
- int b;
- float c;
- char d;
- };</span>
sizeof(A)是12.
3、若類中包含虛函數,則無論有幾個虛函數,sizeof類都等於sizeof(數據成員)的和+sizeof(V表指針,爲4),如:
- <span style="font-size:18px;">class Base
- {
- public:
- Base(){cout<<"Base-ctor"<<endl;}
- ~Base(){cout<<"Base-dtor"<<endl;}
- int a;
- virtual void f(int) {cout<<"Base::f(int)"<<endl;}
- virtual void f(double){cout<<"Base::f(double)"<<endl;}//共有兩個虛擬函數(virtual)
- };</span>
sizeof(Base)爲8.
4、對於子類,它的sizeof是它父類成員(無論成員是public或private),再加上它自己的成員,對齊後的sizeof,如:
- <span style="font-size:18px;">class A2
- {
- public:
- int a;
- private:
- char b;
- };
- class A3:public A2
- {
- public:
- char b;
- short a; }</span>
sizeof(A3)是12.
5、對於子類和父類中都有虛函數的情況,子類的sizeof是它父類成員(無論成員是public或private),再加上它自己的成員,對齊後的sizeof,再加4(虛表指針)。如:
- <span style="font-size:18px;">class Base
- {
- public:
- Base(){cout<<"Base-ctor"<<endl;}
- ~Base(){cout<<"Base-dtor"<<endl;}
- int a;
- virtual void f(int) {cout<<"Base::f(int)"<<endl;}
- virtual void f(double){cout<<"Base::f(double)"<<endl;}
- };
- class Derived:public Base
- {
- public:
- Derived(){cout<<"Derived-ctor"<<endl;}
- int b;
- virtual void g(int){cout<<"Derived::g(int)"<<endl;}
- };</span>
sizeof(Derived)是12.
6、對於虛繼承的子類,其sizeof的值是其父類成員,加上它自己的成員,以及它自己一個指向父類的指針(大小爲4),對齊後的sizeof。如:
- <span style="font-size:18px;">#include <iostream.h>
- class a
- {
- private:
- int x;
- };
- class b: virtual public a
- {
- private:
- int y;
- };
- class c: virtual public a
- {
- private:
- int z;
- };
- class d:public b,public c
- {
- private:
- int m;
- };
- int main(int argc,char* argv[])
- {
- cout<<sizeof(<span style="color:#ff0000;">a</span>)<<endl;
- cout<<sizeof(<span style="color:#ff0000;">b</span>)<<endl;
- cout<<sizeof(<span style="color:#ff0000;">c</span>)<<endl;
- cout<<sizeof(<span style="color:#ff0000;">d</span>)<<endl;
- return 0;
- } </span>
在VC6.0下調試結果爲
4
12
12
24
sizeof(b)和sizeof(c)相同,都是4+4+4=12。
sizeof(d)是sizeof(b)(爲12)+sizeof(c)(爲12)-b和c相同的部分(a的成員,大小是4)+d自己的成員(大小爲4)=24
7、對於既有虛繼承又有虛函數的子類,其sizeof的值是其父類成員(計算虛表指針大小+4),加上它自己的成員(計算虛表指針大小+4),以及它自己一個指向父類的指針(大小爲4),對齊後的sizeof。
- <span style="font-size:18px;">class Base
- {
- public:
- Base(){cout<<"Base-ctor"<<endl;}
- ~Base(){cout<<"Base-dtor"<<endl;}
- virtual void f(int) {cout<<"Base::f(int)"<<endl;}
- virtual void f(double){cout<<"Base::f(double)"<<endl;}
- };
- class Derived:virtual public Base
- {
- public:
- Derived(){cout<<"Derived-ctor"<<endl;}
- virtual void g(int){cout<<"Derived::g(int)"<<endl;}
- };</span>
sizeof(Base)=4
sizeof(Derived)=12 (父類虛表指針大小4+自己虛表指針大小4+子類指向父類的一個指針大小4=12)
七、C結構體之位域(位段)的sizeof
有些信息在存儲時,並不需要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。爲了節省存儲空間,並使處理簡便,C語言又提供了一種數據結構,稱爲“位域”或“位段”。所謂“位域”是把一個字節中的二進位劃分爲幾個不同的區域, 並說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。 這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。
(一)位域的定義和位域變量的說明位域定義與結構定義相仿,其形式爲:
struct 位域結構名
{
位域列表
};
其中位域列表的形式爲:
類型說明符 位域名:位域長度
位域變量的說明與結構變量說明的方式相同。 可採用先定義後說明,同時定義說明或者直接說明這三種方式。例如:
- <span style="font-size:18px;">struct bs
- {
- int a:8;
- int b:2;
- int c:6;
- }data; </span>
說明data爲bs變量,共佔兩個字節。其中位域a佔8位,位域b佔2位,位域c佔6位。對於位域的定義尚有以下幾點說明:
1. 一個位域必須存儲在同一個字節中,不能跨兩個字節。如一個字節所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:
- <span style="font-size:18px;">struct bs
- {
- unsigned a:4
- unsigned b:5 /*從下一單元開始存放*/
- unsigned c:4
- }</span>
2. 由於位域不允許跨兩個字節,因此位域的長度不能大於一個字節的長度。
3. 位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:
- <span style="font-size:18px;">struct k
- {
- int a:1
- int :2 /*無位域名,該2位不能使用*/
- int b:3
- int c:2
- }; </span>
(二)位域的使用
- <span style="font-size:18px;">#include <iostream>
- #include <memory.h>
- using namespace std;
- struct A
- {
- int a:5;
- int b:3;
- };
- int main(void)
- {
- char str[100] = "0134324324afsadfsdlfjlsdjfl";
- struct A d;
- memcpy(&d, str, sizeof(A));
- cout << d.a << endl;
- cout << d.b << endl;
- return 0;
- }</span>
複製代碼
在32位x86機器上輸出:
$ ./langxun.exe
-16
1
解析:在默認情況下,爲了方便對結構體內元素的訪問和管理,當結構體內的元素長度都小於處理器的位數的時候,便以結構體裏面最長的元素爲對其單位,即結構體的長度一定是最長的數據元素的整數倍;如果有結構體內存長度大於處理器位數的元素,那麼就以處理器的位數爲對齊單元。由於是32位處理器,而且結構體中a和b元素類型均爲int(也是4個字節),所以結構體的A佔用內存爲4個字節。
上例程序中定義了位域結構A,兩個個位域爲a(佔用5位),b(佔用3位),所以a和b總共佔用了結構A一個字節(低位的一個字節)。
當程序運行到14行時,d內存分配情況:
高位 00110100 00110011 00110001 00110000 低位(ASCII碼)
'4' '3' '1' '0'
其中d.a和d.b佔用d低位一個字節(00110000),d.a : 10000, d.b : 001
d.a內存中二進制表示爲10000,由於d.a爲有符號的整型變量,輸出時要對符號位進行擴展,所以結果爲-16(二進制爲11111111111111111111111111110000)
d.b內存中二進制表示爲001,由於d.b爲有符號的整型變量,輸出時要對符號位進行擴展,所以結果爲1(二進制爲00000000000000000000000000000001)
(三)位域的對齊
如果結構體中含有位域(bit-field),那麼VC中準則是:
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將從新的存儲單元開始,其偏移量爲其類型大小的整數倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式(不同位域字段存放在不同的位域類型字節中),Dev-C++和GCC都採取壓縮方式;
系統會先爲結構體成員按照對齊方式分配空間和填塞(padding),然後對變量進行位域操作。