全面總結sizeof的用法(定義、語法、指標變數、陣列、結構體、類、聯合體、位域位段)
一、前言
編譯環境是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),然後對變數進行位域操作。