說明:
sizeof在筆試面試的時候頻頻地出現,這也是對基礎的一個考查。關於sizeof的文章很多,但感覺大家都沒有好好總結下,本着“先行先贏”和“爲人民服務”的精神,查找引用參考了很多文章,在這裏總結一下,有錯誤或者遺漏的地方還得請高手多多指教,也不要因這這些問題誤導別人,希望以後大家在學習的過程中也能節省些時間。
概要
sizeof是C語言的一種單目操作符(但有人也不這麼以爲,認爲它是一種特殊的宏),如C語言的其他操作符++、--等。它並不是函數(這是必須的)。sizeof操作符以字節形式給出了其操作數的存儲大小。操作數可以是一個表達式或括在括號內的類型名。操作數的存儲大小由操作數的類型決定,簡單的說其作用就是返回一個對象或者類型所佔的內存字節數。
sizeof的使用方法
1、用於數據類型(包括自定義類型)
sizeof使用形式:sizeof(type)
數據類型必須用括號括住。如sizeof(int)。
2、用於變量
sizeof使用形式:sizeof(var_name)或sizeof var_name
變量名可以不用括號括住。如sizeof (var_name),sizeof var_name等都是正確形式。帶括號的用法更普遍,大多數程序員採用這種形式。
[注意]sizeof操作符不能用於函數類型,不完全類型或位字段。不完全類型指具有未知存儲大小的數據類型,如未知存儲大小的數組類型、未知內容的結構或聯合類型、void類型等。
如sizeof(max)若此時變量max定義爲int max(),sizeof(char_v) 若此時char_v定義爲char char_v [MAX]且MAX未知,sizeof(void)都不是正確形式。
sizeof深入理解及陷阱分析
數據類型的sizeof
(1)應用
- 固有類型:
32位C++中的基本數據類型,也就char,short int(short),int,long int(long),float,double, long double
大小分別是:1,2,4,4,4,8, 10。
- 自定義類型:
typedef short WORD;
typedef long DWORD;
(2)舉例及陷阱
- e.g. 1.1
// 32位機上int長度爲4
cout<<sizeof(int)<<endl;
// == 操作符返回bool類型,相當於 cout<<sizeof(bool)<<endl;
cout<<sizeof(1==2)<<endl;
在編譯階段已經被翻譯爲:
cout<<4<<endl;
cout<<1<<endl;
- e.g. 1.2
int a = 0;
cout<<sizeof(a=3)<<endl;
cout<<a<<endl;
輸出是4,0。而不是期望中的4,3。
就在於sizeof在編譯階段處理的特性。由於sizeof不能被編譯成機器碼,所以sizeof作用範圍內,也就是()裏面的內容也不能被編譯,而是被替換成類型。=操作符返回左操作數的類型,所以a=3相當於int,而代碼也被替換爲:
int a = 0;
cout<<4<<endl;
cout<<a<<endl;
所以,sizeof是不可能支持鏈式表達式的,這也是和一元操作符不一樣的地方。
結論:不要把sizeof當成函數,也不要看作一元操作符,把他當成一個特殊的編譯預處理。
- e.g. 1.3
int i = 2;
cout<<sizeof(i)<<endl; // sizeof(object)的用法,合理
cout<<sizeof i<<endl; // sizeof object的用法,合理
cout<<sizeof 2<<endl; // 2被解析成int類型的object, sizeof object的用法,合理
cout<<sizeof(2)<<endl; // 2被解析成int類型的object, sizeof(object)的用法,合理
cout<<sizeof(int)<<endl;// sizeof(typename)的用法,合理
cout<<sizeof int<<endl; // 錯誤!對於操作符,一定要加()
可以看出,加()是永遠正確的選擇。
結論:不論sizeof要對誰取值,最好都加上()。
- e.g. 1.4
cout<<sizeof(unsigned int) == sizeof(int)<<endl; // 相等,輸出 1
unsigned影響的只是最高位bit的意義,數據長度不會被改變的。
結論:unsigned不能影響sizeof的取值。
- e.g. 1.5
typedef short WORD;
typedef long DWORD;
cout<<(sizeof(short) == sizeof(WORD))<<endl; // 相等,輸出1
cout<<(sizeof(long) == sizeof(DWORD))<<endl; // 相等,輸出1
結論:自定義類型的sizeof取值等同於它的類型原形。
- e.g. 1.6
cout << sizeof(2 + 3.14) << endl;
3.14的類型爲double,2也會被提升成double類型,所以等價於 sizeof( double );
所以最後的結果是:8
函數類型的sizeof
sizeof也可以對一個函數調用求值,其結果是函數返回類型的大小,函數並不會被調用,並且返回值不能是void(當然也就是沒有返回值)。
C99標準規定,函數、不能確定類型的表達式以及位域(bit-field)成員不能被計算sizeof值。
- e.g. 2.1
int f1(){return 0;};
double f2(){return 0.0;}
void f3(){}
cout<<sizeof(f1())<<endl; // f1()返回值爲int,因此被認爲是int
cout<<sizeof(f2())<<endl; // f2()返回值爲double,因此被認爲是double
cout<<sizeof(f3())<<endl; // 錯誤!無法對void類型使用sizeof
cout<<sizeof(f1)<<endl; // 錯誤!無法對函數指針使用sizeof
指針類型的sizeof
學過數據結構的你應該知道指針是一個很重要的概念,它記錄了另一個對象的地址。既然是來存放地址的,那麼它當然等於計算機內部地址總線的寬度。所以在32位計算機中,一個指針變量的返回值必定是4(注意結果是以字節爲單位),可以預計,在將來的64位系統中指針變量的sizeof結果爲8。
- e.g. 3.1
char* pc = "abc";
int* pi;
string* ps;
char** ppc = &pc;
void (*pf)();// 函數指針
sizeof( pc ); // 結果爲4
sizeof( pi ); // 結果爲4
sizeof( ps ); // 結果爲4
sizeof( ppc ); // 結果爲4
sizeof( pf );// 結果爲4
可以看到,不管是什麼類型的指針,大小都是4的,因爲指針就是32位的物理地址。
結論:只要是指針,大小就是4。(64位機上要變成8也不一定)。
數組類型的sizeof
數組類型的sizeof常常就和char*, char[],這些東西在一起就會誤導人,這個到最後再討論,先從例子看起。
- e.g. 4.1
char a[] = "abcdef";
int b[20] = {3, 4};
char c[2][3] = {"aa", "bb"};
cout<<sizeof(a)<<endl; // 7
cout<<sizeof(b)<<endl; // 80
cout<<sizeof(c)<<endl; // 6
數組a的大小在定義時未指定,編譯時給它分配的空間是按照初始化的值確定的,也就是7,注意:在最後還有一個“\0”也算一位。所以是6+1=7。
本人在VS2005上得到b的結果是80,可以看出,數組的大小就是他在編譯時被分配的空間,也就是各維數的乘積*數組元素的大小。
c是多維數組,佔用的空間大小是各維數的乘積,也就是6。
結論:數組的大小是各維數的乘積*數組元素的大小。
- e.g. 4.2
int *d = new int[10];
cout<<sizeof(d)<<endl; // 4
d是我們常說的動態數組,但是他實質上還是一個指針,所以sizeof(d)的值是4。
- e.g. 4.3
double* (*a)[3][6];
cout<<sizeof(a)<<endl; // 4
cout<<sizeof(*a)<<endl; // 72
cout<<sizeof(**a)<<endl; // 24
cout<<sizeof(***a)<<endl; // 4
cout<<sizeof(****a)<<endl; // 8
這個相對麻煩些,得先了解下數組指針和指針數組的知識。
a是一個很奇怪的定義,他表示一個指向 double*[3][6]類型數組的指針。既然是指針,所以sizeof(a)就是4。
既然a是執行double*[3][6]類型的指針,*a就表示一個double*[3][6]的多維數組類型,因此sizeof(*a)=3*6*sizeof(double*)=72。
同樣的,**a表示一個double*[6]類型的數組,所以sizeof(**a)=6*sizeof(double*)=24。
***a就表示其中的一個元素,也就是double*了,所以sizeof(***a)=4。
至於****a,就是一個double了,所以sizeof(****a)=sizeof(double)=8。
向函數傳遞數組的sizeof
首先,我們要明確數組作爲參數被傳給函數時傳的是指針而不是數組,傳遞的是數組的首地址。也可以說是它退化成了一個指針。
- e.g. 5.1
#include <iostream>
using namespace std;
int Sum(int i[])
{
int sumofi = 0;
for (int j = 0; j < sizeof(i)/sizeof(int); j++) //實際上,sizeof(i) = 4
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[6] = {21, 22, 22, 19, 34, 12};
cout<<Sum(allAges)<<endl;
system("pause");
return 0;
}
Sum的本意是用sizeof得到數組的大小,然後求和。但是實際上,傳入自函數Sum的,只是一個int 類型的指針,所以sizeof(i)=4,而不是24,所以會產生錯誤的結果。解決這個問題的方法使是用指針或者引用。
使用指針的情況:
int Sum(int (*i)[6])
{
int sumofi = 0;
for (int j = 0; j < sizeof(*i)/sizeof(int); j++) //sizeof(*i) = 24
{
sumofi += (*i)[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(&allAges)<<endl;
system("pause");
return 0;
}
在這個Sum裏,i是一個指向i[6]類型的指針,注意,這裏不能用int Sum(int (*i)[])聲明函數,而是必須指明要傳入的數組的大小,不然sizeof(*i)無法計算。但是在這種情況下,再通過sizeof來計算數組大小已經沒有意義了,因爲此時大小是指定爲6的。
使用引用的情況和指針相似:
int Sum(int (&i)[6])
{
int sumofi = 0;
for (int j = 0; j < sizeof(i)/sizeof(int); j++)
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(allAges)<<endl;
system("pause");
return 0;
}
這種情況下sizeof的計算同樣無意義,所以用數組做參數,而且需要遍歷的時候,函數應該有一個參數來說明數組的大小,而數組的大小在數組定義的作用域內通過sizeof求值。
因此上面的函數正確形式應該是:
#include <iostream>
using namespace std;
int Sum(int *i, unsigned int n)
{
int sumofi = 0;
for (int j = 0; j < n; j++)
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(i, sizeof(allAges)/sizeof(int))<<endl;
system("pause");
return 0;
}
結構體的sizeof
結構體的sizeof相對還是麻煩一些,寫了一篇專門的文章僅供參考:
鏈接:http://pppboy.blog.163.com/blog/static/30203796201082494026399/
類的sizeof
[提示]這個可以看看孫鑫的那個《深入淺出MFC》的第一章的那個C++基礎部分,就有這個類的大小的一個例子很不錯。
先從例子看起吧。
關於虛繼承也有個總結:
http://pppboy.blog.163.com/blog/static/30203796201041005953744/
- e.g. 7.1
//空類,相當於一個空結構體
class A
{
};
//和結構體相同
class B
{
int a;
double b;
};
//有一個非虛函數
class C
{
int a;
double b;
void f(){}
};
//有1個虛函數
class D
{
int a;
double b;
virtual void vf(){}
};
//有2個虛函數
class E
{
int a;
double b;
virtual void vf(){}
virtual void vf2(){}
};
cout << "A: " << sizeof(A) << endl;
cout << "B: " << sizeof(B) << endl;
cout << "C: " << sizeof(C) << endl;
cout << "D: " << sizeof(D) << endl;
cout << "E: " << sizeof(E) << endl;
在8字節對齊的情況下結果爲:
A: 1
B: 16
C: 16
D: 24
E: 24
請按任意鍵繼續. . .
可見:
(1)類的大小和結構體大小一樣
(2)非虛擬成員函數不佔大小
(3)如果有虛擬函數,則會多佔用4個字節(虛擬函數表)
- e.g. 7.2
class A
{
int a;
double b;
void f(){}
virtual void vf(){}
};
class B :public A
{
};
class C : public virtual A
{
};
class D
{
char a;
};
class E
{
char a;
virtual void vfd(){}
};
class F1 : public A, public D
{
};
class F2 : public A, public E
{
};
class G1 : public virtual A, public virtual D
{
};
class G2 : public virtual A, public virtual E
{
};
cout << "A: " << sizeof(A) << endl;
cout << "B: " << sizeof(B) << endl;
cout << "C: " << sizeof(C) << endl;
cout << "D: " << sizeof(D) << endl;
cout << "E: " << sizeof(E) << endl;
cout << "F1: " << sizeof(F1) << endl;
cout << "F2: " << sizeof(F2) << endl;
cout << "G1: " << sizeof(G1) << endl;
cout << "G2: " << sizeof(G2) << endl;
在8字節對齊的情況下,結果爲:
A: 24
B: 24
C: 32
D: 1
E: 8
F1: 32
F2: 32
G1: 33
G2: 40
請按任意鍵繼續. . .
結論:
(1)在繼承的時候,單一繼承和多重繼承的大小一樣,也就相當於子類有一個自己的虛函數表,這個虛函數表是自己重新定義的,相當於它把父親的虛函數表改成自己的,並且只有一個。
(2)在有虛繼承時,涉及虛指針的問題,也就是說,他會用父類的虛函數表,如果父類沒有,那麼他也就沒有這個父類的虛函數表,相當於他不具有自己獨立的虛函數表,而是用它父親的,有幾個父親有表,那麼它都可以隨時用它父親的,也算是他自己的大小。
- e.g. 7.3
父類指針指向子類的問題
class Base
{
int a;
virtual void vf(){};
};
class Derived : public Base
{
char a;
};
Base* pBase = new Derived;
Derived* pDerived = new Derived;
cout << "Base: " << sizeof(Base) << endl;//8
cout << "Derived: " << sizeof(Derived) << endl;//12
cout << "pBase: " << sizeof(pBase) << endl;//指針大小都爲4
cout << "*pBase: " << sizeof(*pBase) << endl;//8
cout << "pDerived: " << sizeof(pDerived) << endl;//指針大小都爲4
cout << "*pDerived:" << sizeof(*pDerived) << endl;//12
//在8字節對齊時的結果爲:
Base: 8
Derived: 12
pBase: 4
*pBase: 8
pDerived: 4
*pDerived:12
請按任意鍵繼續. . .
結論:sizeof 操作符不能返回動態地被分派了的數組或外部的數組的尺寸。它返回的只是類型的大小(可以這麼理解)
字符串的sizeof和strlen
這個在實際的應用中是最容易忽悠人的,應該好好滴留意一下。
- e.g. 8.1
char a[] = "abcdef";
char b[20] = "abcdef";
string s = "abcdef";
cout<<strlen(a)<<endl; // 6,字符串長度
cout<<sizeof(a)<<endl; // 7,字符串容量
cout<<strlen(b)<<endl; // 6,字符串長度
cout<<strlen(b)<<endl; // 20,字符串容量
cout<<sizeof(s)<<endl; // 12, 這裏不代表字符串的長度,而是string類的大小
cout<<strlen(s)<<endl; // 錯誤!s不是一個字符指針。
a[1] = '\0';
cout<<strlen(a)<<endl; // 1
cout<<sizeof(a)<<endl; // 7,sizeof是恆定的
strlen是尋找從指定地址開始,到出現的第一個0之間的字符個數,他是在運行階段執行的
而sizeof是得到數據的大小,在這裏是得到字符串的容量。所以對同一個對象而言,sizeof的值是恆定的。
string是C++類型的字符串,他是一個類,所以sizeof(s)表示的並不是字符串的長度,而是類string的大小。strlen(s)根本就是錯誤的,因爲strlen的參數是一個字符指針,如果想用strlen得到s字符串的長度,應該使用sizeof(s.c_str()),因爲string的成員函數c_str()返回的是字符串的首地址。實際上,string類提供了自己的成員函數來得到字符串的容量和長度,分別是Capacity()和Length()。string封裝了常用了字符串操作,所以在C++開發過程中,最好使用string代替C類型的字符串。
下面引用VCbase裏的內容再探討一下。
(1)sizeof是算符,strlen是函數。
(2)sizeof可以用類型做參數,strlen只能用char*做參數,且必須是以''\0''結尾的。sizeof還可以用函數做參數
short f();
printf("%d\n", sizeof(f()));
(3)數組做sizeof的參數不退化,傳遞給strlen就退化爲指針了。
char var[10];
int test(char var[])
{
return sizeof(var);
}
var[]等價於*var,已經退化成一個指針,所以大小是4.
(4)大部分編譯程序 在編譯的時候就把sizeof計算過了 是類型或是變量的長度這就是sizeof(x)可以用來定義數組維數的原因
char str[20]="0123456789";
int a=strlen(str); //a=10;
int b=sizeof(str); //而b=20;
(5)strlen的結果要在運行的時候才能計算出來,時用來計算字符串的長度,不是類型佔內存的大小。
(6)sizeof後如果是類型必須加括弧,如果是變量名可以不加括弧。這是因爲sizeof是個操作符不是個函數。
(7)當適用了於一個結構類型時或變量, sizeof 返回實際的大小, 當適用一靜態地空間數組, sizeof 歸還全部數組的尺寸。 sizeof 操作符不能返回動態地被分派了的數組或外部的數組的尺寸
(8)數組作爲參數傳給函數時傳的是指針而不是數組,傳遞的是數組的首地址,如:
fun(char [8])
fun(char [])
都等價於 fun(char *) 在C++裏傳遞數組永遠都是傳遞指向數組首元素的指針,編譯器不知道數組的大小如果想在函數內知道數組的大小, 需要這樣做:進入函數後用memcpy拷貝出來,長度由另一個形參傳進去.
fun(unsiged char *p1, int len)
{
unsigned char* buf = new unsigned char[len+1]
memcpy(buf, p1, len);
}//看來這個《程序員面試寶典》也是抄這段文字了。。。
和我一起做題目
容易迷糊的幾個問題
- e.g. 1.1
char* ss = "0123456789";
sizeof(ss) 結果 4 ss是指向字符串常量的字符指針
sizeof(*ss) 結果 1 *ss是第一個字符
- e.g. 1.2
char ss[] = "0123456789";
sizeof(ss) 結果 11 ss是數組,計算到\0位置,因此是10+1
sizeof(*ss) 結果 1 *ss是第一個字符
- e.g. 1.3
char ss[100] = "0123456789";
sizeof(ss) 結果是100, ss表示在內存中的大小 100×1
strlen(ss) 結果是10,strlen是個函數內部實現是用一個循環計算到\0爲止之前
- e.g. 1.4
int ss[100] = "0123456789";
sizeof(ss) 結果 400 ,ss表示再內存中的大小 100×4
strlen(ss) 錯誤 ,strlen的參數只能是char* 且必須是以''\0''結尾的
- e.g. 1.5
char q[]="abc";
char p[]="a\n";
sizeof(q),sizeof(p),strlen(q),strlen(p);
結果是 4 3 3 2
這個容易迷糊,好好看清楚。。注意sizeof(p)是“\n”算一位,而strlen(p)在“\n”的時候就已經結束
e.g. 1.6
char szPath[MAX_PATH]
如果在函數內這樣定義,那麼sizeof(szPath)將會是MAX_PATH,但是將szPath作爲虛參聲明時(void fun(char szPath[MAX_PATH])),sizeof(szPath)卻會是4(指針大小)
還是退化成指針的問題
網上找到的幾個牛B點的
- e.g. 2.1
1. sizeof(char) =
2. sizeof 'a' =
3. sizeof "a" =
4. strlen("a")) =
答案:1,1,2,1(說明:原來作者給的答案是1,4,2,1。但第二個應該是1而不是4)
- e.g. 2.2
short (*ptr[100])[200];
1. sizeof(ptr) =
2. sizeof(ptr[0]) =
3. sizeof(*ptr[0]) =
4. sizeof((*ptr[0])[0])) =
答案:400,4,400,2
這裏我們定義了一個100個指針數組,每個指針均指向有200個元素的數組,其內存佔用爲200*sizeof(short)字節。那麼這100個數組指針的大小sizeof(ptr)爲100*sizeof(short*)。接着,指針數組的第一個指針ptr[0]指向第一個數組,所以這個指針ptr[0]的大小實際上就是一個普通指針的大小,即sizeof(short*)。*ptr[0]指向第一個數組的起始地址,所以sizeof(*ptr[0])實際上求的是第一個組的內存大小200*sizeof(short)。(*ptr[0])[0])是第一個數組的第一個元素,因爲是short型,所以這個元素的大小sizeof((*ptr[0])[0]))等價於sizeof(short)。
e.g. 2.3
#include <stdio.h>
#pragma pack(push)
#pragma pack(2)
typedef struct _fruit
{
char apple;
int banana;
short orange;
double watermelon;
unsigned int plum:5;
unsigned int peach:28;
char* tomato;
struct fruit* next;
} fruit;
#pragma pack(4)
typedef struct _fruit2
{
char apple;
int banana;
short orange;
double watermelon;
unsigned int plum:5;
unsigned int peach:28;
char* tomato;
struct fruit2* next;
} fruit2;
#pragma pack(pop)
int main(int argc, char *argv[])
{
printf("fruit=%d,fruit2=%d\n",sizeof(fruit),sizeof(fruit2));
}
答案:fruit=30,fruit2=36
聽聽作者怎麼說:
如果你回答錯誤,那麼你對數據結構的對齊還沒有吃透。這裏#pragma pack(2)強制設置編譯器對齊屬性爲2,所以第一個數據結構以2對齊,sizeof(fruit)=(sizeof(apple)+1)+sizeof(banana)+sizeof(orange)+sizeof(watermelon)+((plum:5bit+peach:28bit+15bit)/8bit)+sizeof(tomato)+sizeof(next)(注意式子中1 和 15bit 表示補齊內存,使其以2對齊,),既sizeof(fruit)=(sizeof(char)+1)+sizeof(int)+sizeof(short)+sizeof(double)+sizeof(char*)+sizeof(struct fruit*)。第一個數據結構聲明完了之後,又使用#pragma pack(4)強制設置編譯器對齊屬性爲4,所以同理,可以得到sizeof(fruit2)=(sizeof(char)+3)+sizeof(int)+(sizeof(short)+2)+sizeof(double)+((5bit+28bit+31bit)/8bit)+sizeof(char*)+sizeof(struct fruit2*)。
注:#pragma pack(push)保存默認對齊,#pragma pack(pop)恢復默認對齊。
(這個沒有細細看,太累了,明天再看這個例子)
引用申明
有文章在引用了其它作者的很多文字,所有權都歸原作者所有,這裏僅供本人總結學習,下面給出參考過或者引用過的鏈接,謝謝作者。
1.http://edu.codepub.com/2009/1117/17745.php
2.http://dev.yesky.com/143/2563643.shtml
3.http://blog.chinaunix.net/u2/62684/showart_489736.html
4.http://www.vckbase.com/document/viewdoc/?id=1054
5.《程序員面試寶典》第二版 p52-p63
本文出處:http://pppboy.blog.163.com/blog/static/302037962010825105652390/