轉自:http://krystism.is-programmer.com/
學過c的都知道sizeof運算符。不過還是需要注意以下幾點。先從c的sizeof說起:
1. sizeof 是運算符,而不是函數。雖然我們習慣sizeof(...),但( )並不是必需的,它只是表示優先級。我們把sizeof后面的目標叫對象或者操作數。本文約定就叫sizeof對象。
2. 當sizeof 的對象是表達式時,求的大小是表達式返回值的類型大小,但並不計算表達式的值,比如
1
2
3
4
|
char c = 1;
int i = 2;
cout << sizeof (c + i) << endl;
cout << sizeof (c = c + i) << endl;
|
前者c + i會隱式類型轉化為int類型(類型提升),因此返回4(32位系統), 而后者雖然運算時也是轉化為int,但賦值給c時又會轉化為char,因此返回的是1。同樣如果對象是函數,則返回函數返回值類型大小,如:
1
2
3
4
5
6
7
8
9
10
11
|
long long foo()
{
printf ( "'%s' has been called.\n" , __func__);
return 0;
}
int main( int argc, char **argv)
{
cout << sizeof (foo()) << endl;
return 0;
}
|
執行后輸出8, 不會輸出 'foo' has been called.說明函數沒有真正執行,而只是判斷了下返回類型。
3.注意sizeof 對象是指針和數組的區別。
當sizeof的對象是數組時,返回數組總大小,而當對象是指針時,返回指針本身的大小,而不是指示內存空間的大小。因為指針本身就是一個無符號整型數, 因此int *p ,sizeof(p)返回的大小是sizeof(void *), 32 位系統返回4,即32位。但注意當數組名作為實參傳入函數時,會自動轉化為指針類型,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void foo( int a[])
{
cout << sizeof (a) << endl; /* 4 */
}
int main( int argc, char **argv)
{
int a[] = {1, 2, 3, 4};
int *p = a;
cout << sizeof (a) << endl; /* 16 */
cout << sizeof (p) << endl; /* 4 */
foo(a);
return 0;
}
|
4. sizeof 無法獲取動態分配的內存大小,即使用malloc動態的分配內存,無法使用sizeof獲取其大小。
5. 注意c_style字符串末尾有一個\0結束符,也需要占一個char空間,因此sizeof("1") 返回2。而strlen返回的是字符數,不包括\0結束符。
6.關於結構體類型。
理論上一個結構體所占空間是所有成員的大小總和,但由於考慮到對齊問題,會有填充字節。
1
2
3
4
5
|
struct node
{
int a;
char c;
};
|
大小為8字節而不是5字節,填充了3字節。
注意:c語言中空struct大小為0, 而c++中空struct 大小為1, 具體看后面關於空類的討論。另外,c99中結構體后面的動態數組,即不指定大小的數組,sizeof 時不包括動態數組的大小,即
1
2
3
4
5
6
|
struct node
{
int a;
char c;
int d[];
};
|
返回依然是8。
下面關於c++類的討論。除了struct ,以上討論關於c的sizeof同樣適合於c++。首先說說c++ 中的struct類型,注意和c中的struct是不一樣的,c中的struct只是一種把各種基本數據類型包裝的組合類型,而c++的struct本質 上是類,即類有的東西,struct基本都有,即struct也有構造函數、析構函數、成員函數等等,不過它的默認成員是public的,而class定 義的類成員默認是private的。另外,struct繼承默認也是public,而class定義的類默認是private。另外注意:class可以定義模板參數,但struct不可以!因此,struct本質就是類。
下面主要討論類的大小:
1. 空類的大小。空類型實例中不包含任何信息,應該大小為0. 但是當我們聲明該類型的實例的時候,它必須在內存中占有一定的空間,否則無法使用這些實例。至於占用多少內存,由編譯器決定。g++中每個空類型的實例占1字節空間。注意空struct即空類,這就是為什么c++的空struct占一個字節的原因。
2. 構造函數、析構函數、成員函數調用時只需知道函數地址即可,而這些函數的地址之與類型相關,而與具體的實例無關,因此不會在實例中額外添加任何信息。
3. 靜態數據成員放在全局數據成員中,它不占類實例大小,多個類實例只有一個實體。可以看作是一種特殊的全局變量。
綜上1,2,3:
1
2
3
4
5
6
7
8
9
|
class A
{
public :
static int a;
static char c;
A(){};
~A(){};
void foo(){};
};
|
類A的大小為1字節,等於空類大小,因此靜態數據成員a,c和成員函數都不占類的大小。
4. 類的非靜態數據成員和c語言中的struct類似,也需要對齊,可能需要字節填充。
1
2
3
4
5
6
7
8
9
|
class
A
{
public
:
int
a;
char
c;
A(){};
~A(){};
void
foo(){};
};
|
類A的大小為8字節,a占4B,c占1B,填充3B。
5. 如果一個類中有虛函數,則該類型會生成一個虛函數表,並在該類型的每一個實例中添加一個指向虛函數表的指針,因此類大小必須加上一個指針所占的空間。如果是普通繼承,子類和基類共享這個指針。
1
2
3
4
5
6
7
8
9
10
|
class A
{
public :
int a;
char c;
A(){};
~A(){};
void foo(){};
void virtual bar(){};
};
|
類A的大小為12B。數據成員8B,加上指向虛擬函數表的指針。注意,是在32位系統上。如果是64位機器,一個指針占8B。
6.虛繼承時,派生類會生成一個指向虛基類表的指針,占一個指針大小空間。如果還有虛函數,不增加額外指針大小空間,原因不太清楚,如果誰知道,請一定要告訴我!如下:
1
2
3
4
5
6
7
8
9
|
class A
{
int a;
};
class B: public virtual A
{
int b;
virtual void foo(){};
};
|
類B的大小為12B,數據成員b占4B,從A中繼承a也占4B,另外一個由於virtual存在,額外加一個指針大小4B,共12B。所以:只要有virtual,無論是在成員函數,還是在繼承上,都額外加一個指針大小空間。
基本就這些了,如果有紕漏,請指出,謝謝!