C/C++基礎之sizeof使用

2020-08-16 11:08:13

在 C/C++ 中,sizeof() 是一個判斷數據型別或者表達式長度的運算子。

1 sizeof 定義
sizeof 是 C/C++ 中的一個操作符(operator),返回一個物件或者型別所佔的記憶體位元組數。

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type(including aggregate types). This keyword returns a value of type size_t. ——來自MSDN

其返回值型別爲 size_t ,在標頭檔案 stddef.h 中定義爲: typedef unsigned int size_t;

從sizeof 的定義可以看出:sizeof 不是一個函數,因爲函數呼叫必須有一對括號。

#include <stdio.h>

int main(void)
{
int num = 97;

printf(「sizeof(num = 0)的值:%d\n」,sizeof(num = 0));
printf(「num 的值:%d\n」,num);
return 0;
}

執行結果爲4,97;並不是4,0
說明:sizeof 不是標準意義上的一元操作符,不支援鏈式表達式,sizeof 作用域範圍內的語句不會編譯成機器碼,如 sizeof(num++) 中的 ++ 不執行。sizeof 也不是函數, sizeof 更像一個特殊的宏,在編譯階段求值。

2 sizeof 用法
sizeof 有兩種語法形式,如下:

sizeof(type_name); //sizeof(型別);
sizeof (object); //或sizeof object 都屬於 sizeof物件;
所以:

int i;
sizeof(i); //合理
sizeof i; //合理
sizeof(int); //合理
sizeof int; //不合理
對型別使用 sizeof 時,sizeof type_name 是非法的,必須寫爲 sizeof(type_name);

無論是對物件還是型別取值,sizeof () 這種形式都是對的;

1 基本數據型別的 sizeof
這裏的基本數據型別是指short、int、long、float、double這樣的簡單內建數據型別。

由於它們的記憶體大小是和系統相關的,所以在不同的系統下取值可能不同。

#include
using namespace std;

int main()
{
cout << "Size of char : " << sizeof(char) << endl;
cout << "Size of int : " << sizeof(int) << endl;
cout << "Size of short int : " << sizeof(short int) << endl;
cout << "Size of long int : " << sizeof(long int) << endl;
cout << "Size of float : " << sizeof(float) << endl;
cout << "Size of double : " << sizeof(double) << endl;
cout << "Size of wchar_t : " << sizeof(wchar_t) << endl;
return 0;
}
在 32 位系統下內建數據型別與其 sizeof 運算結果如下:

Size of char : 1
Size of int : 4
Size of short int : 2
Size of long int : 4
Size of float : 4
Size of double : 8
Size of wchar_t : 4
unsigned 不影響內建型別 sizeof 的取值

2 指針型別的 sizeof
指針主要用於儲存地址,前幾天文章C語言指針詳解提到過,指針變數的位寬等於機器字長,機器字長由 CPU 暫存器位數決定。在 32 位系統中,一個指針變數的返回值爲 4 位元組, 64 位系統中指針變數的 sizeof 結果爲 8 位元組。

char *p =」hello」;
sizeof( p ); // 結果爲4
sizeof(*p); // 結果爲1
int *pi;
sizeof( pi ); //結果爲4
sizeof(*pi); //結果爲4
char **pp = &p;
sizeof( pp ); // 結果爲4
sizeof( *pp ); // 結果爲4
指針變數的 sizeof 值與指針所指的物件型別沒有任何關係,與指針申請多少空間沒有關係,所有的指針變數所佔記憶體大小均相等。

如果使用 32 位編譯器編譯得到程式是 32 位,那麼在 64bits 系統下,指針變數大小仍然是 4 個位元組。

3 函數型別的 sizeof
函數型別以其返回型別作爲自身型別,進行 sizeof 取值。

void fun1()
{
}
int fun2()
{
return 0;
}
double fun3()
{
return 0.0;
}
cout << sizeof(fun1()) << endl; //錯誤!無法對void型別使用sizeof
cout << sizeof(fun2()) << endl; //fun2()返回值型別爲int,輸出4
cout << sizeof(fun3()) << endl; //fun3()返回值型別爲double,輸出8
注意:不能對返回 void 函數和函數指針進行 sizeof 取值。

4 陣列型別的 sizeof
當 sizeof 作用於陣列時,求取的是陣列所有元素所佔用的大小。

int A[3][5];
char c[]="abcdef";
double*(*d)[3][6];

cout<<sizeof(A)<<endl;      //輸出60
cout<<sizeof(A[4])<<endl;   //輸出20
cout<<sizeof(A[0][0])<<endl;//輸出4
cout<<sizeof(c)<<endl;      //輸出7
cout<<sizeof(d)<<endl;      //輸出4
cout<<sizeof(*d)<<endl;     //輸出72
cout<<sizeof(**d)<<endl;    //輸出24
cout<<sizeof(***d)<<endl;   //輸出4
cout<<sizeof(****d)<<endl;  //輸出8

A 的數據型別是 int[3][5] ,A[4] 的數據型別是 int[5],A[0][0]數據型別是 int 。所以:

sizeof(A)==sizeof(int[3][5])==35sizeof(int)==60
sizeof(A[4])==sizeof(int[5])=5*sizeof(int)==20
sizeof(A[0][0])==sizeof(int)==4
如果字元陣列表示字串,陣列末自動插入 ‘\0’,所以 c 的數據型別是 char[7] ,所以 sizeof©=sizeof(char[7])==7。

d 是一個很奇怪的定義,他表示一個指向 double*[3][6] 型別陣列的指針。既然是指針,所以 sizeof(d) 就是4。

既然 d 是執行 double*[3][6] 型別的指針, d 就表示一個 double[3][6] 的多維陣列型別,因此 sizeof(a)=36sizeof(double)=72 。

*d 表示一個 double[6] 型別的陣列,所以 sizeof(**d)=6sizeof (double)=24。

**d 表示其中的一個元素,也就是 double ,所以 sizeof(***d)=4 。

****d 是一個 double ,所以 sizeof(****d)=sizeof(double)=8。

當陣列作爲函數形參時,下面 下麪輸出結果應該是多少呢?

int GetStrLength(char str[])
{
return sizeof(str);
}

int main()
{
char szStr[] = 「abcdef」;
cout<< GetStrLength() << endl;
return 0;
}
輸出不是 7 ,這裏函數參數 str[] 已不再是陣列型別,而是蛻變成指針,我們呼叫函數 GetStrLength() 時,程式會在棧上分配一個大小爲 7 的陣列嗎?不會!陣列是「傳址」的,呼叫者只需將實參的地址傳遞過去,所以 str 自然爲指針型別 (char*) ,輸出值爲:4 。

陣列的大小是各維數的乘積*陣列元素的大小。

向函數形參傳遞陣列,陣列將會退化爲指針,失去原來陣列的特性。

4 結構體型別的 sizeof
對於 struct 數據結構由 CPU 的對齊問題導致 struct 的大小變得比較複雜。具體可以檢視以前的文章一文輕鬆理解記憶體對齊。

理論上,int 佔 4byte , char 佔一個 byte ,那麼將它們放到一個結構體中應該佔 4+1=5byte ;但是實際上,通過執行程式得到的結果是 8byte 。

#include<stdio.h>

struct{
int x;
char y;
}Test;

int main()
{
printf("%d\n",sizeof(Test)); // 輸出8不是5
return 0;
}
結構體的大小跟結構體成員對齊有密切關係,而並非簡單地等於各個成員的大小之和!比如對如下結構體兩個結構體 A、B 使用 sizeof 的結果分別是:16,24。可以看出 sizeof(B) 並不等於 sizeof(int)+sizeof(double)+sizeof(int)=16 。

struct A
{
int num1;
int num2;
double num3;
};
struct B
{
int num1;
double num3;
int num2;
};
結構體A和B中包含的成員都一樣,只不過順序不同而已,爲什麼其大小不一樣呢?要解釋這個問題,就要瞭解結構體成員對齊的規則。

結構體的大小等於結構體內最大成員大小的整數倍

結構體內的成員的首地址相對於結構體首地址的偏移量是其型別大小的整數倍,比如說 double 型成員相對於結構體的首地址的地址偏移量應該是 8 的倍數。https://www.szcbjs.com/

爲了滿足規則 1 和 2 編譯器會在結構體成員之後進行位元組填充!

從三個規則我們來看看爲什麼 sizeof(B) 等於 24 :首先假設結構體的首地址爲0,第一個成員 num1 的首地址是 0 (滿足規則2),它的型別是 int ,因此它佔用地址空間 0——3 。第二個成員 num3 是 double 型別,它佔用 8 個位元組,由於之前的 num1 只佔用了 4 個位元組,爲了滿足規則 2 ,需要使用規則 3 在 num1 後面填充 4 個位元組(4——7),使得 num3 的起始地址偏移量爲 8 ,因此 num3 佔用的地址空間是:8——15。第三個成員 num2 是 int 型,其大小爲 4 ,由於 num1 和num3 一共佔用了 16 個位元組,此時無須任何填充就能滿足規則 2。因此 num2 佔用的地址空間是 16——19 。那麼是不是結構體的總大小就是 0——19 共 20 個位元組呢?請注意,別忘了規則1!由於結構體內最大成員是 double 佔用 8 個位元組,因此最後還需要在 num2 後面填充 4 個位元組,使得結構體總體大小爲 24 。

struct S{ };
sizeof(S); // 結果爲1
對於一個空 struct 結構體取 sizeof 運算,運算結果爲 1 並非 0 。因爲編譯器爲保證此空 struct 存在,專門分配一個位元組。

如果存在結構體巢狀,無論內層還是外層均需要採用記憶體對齊。

5 類的 sizeof
不含繼承和 static 成員變數的類。

在這種情況下,只需要考慮對齊方式即可。

class A
{
public:
int b;
float c;
char d;
};
class B
{
};

int main(void)
{
cout << 「sizeof(A) is 」 << sizeof(A) << endl;
//輸出結果爲12
cout << 「sizeof(B) is 」 << sizeof(B) << endl;
//輸出結果爲1
return 0 ;
}
空的 class 同樣也佔用 1 個位元組。

計算類物件的大小時,類成員函數不佔用物件空間,只需要考慮類中數據成員的大小。

類中存在靜態成員變數

class A
{
public:
static int a;
int b;
float c;
char d;
};

int main()
{
A object;
cout << 「sizeof(object) is 」 << sizeof(object) << endl;
//輸出結果爲12
return 0 ;
}
因爲在程式編譯期間,就已經爲 static 變數在靜態儲存區域分配了記憶體空間,並且這塊記憶體在程式的整個執行期間都存在。而每次宣告瞭類 A 的一個物件的時候,爲該物件在堆上,根據物件的大小分配記憶體。

類中包含成員函數

class A
{
public:
static int a;
int b;
float c;
char d;
int add(int x,int y)
{
return x+y;
}
};

int main()
{
A object;
cout << 「sizeof(object) is 」 << sizeof(object) << endl;
b = object.add(3,4);
cout << 「sizeof(object) is 」 << sizeof(object) << endl;
//輸出結果爲12
return 0 ;
}
因爲只有非靜態類成員變數在新生成一個object的時候才需要自己的副本。所以每個非靜態成員變數在生成新object需要記憶體,而function是不需要的。

3 sizeof 與 strlen 區別
sizeof 是一個操作符,strlen 是庫函數。

sizeof 的參數可以是數據的型別,也可以是變數,而 strlen 只能以結尾

編譯器在編譯時就計算出了 sizeof 的結果,而 strlen 函數必須在執行時才能 纔能計算出來。並且 sizeof 計算的是數據型別佔記憶體的大小,而 strlen 計算的是字串實際的長度。

陣列做 sizeof 的參數不退化,傳遞給 strlen 就退化爲指針了。如:

int ss[20]=「0123456789」;
sizeof(ss)=80, //ss表示在記憶體中的大小,204。
strlen(ss) //錯誤,strlen的參數只能是char
,且必須是以「\0」結尾的。
char *ss=「0123456789」;
sizeof(ss)=4, //ss是指向字串常數的字元指針。
sizeof(*ss)=1, // *ss是第一個字元。

參考資料