下次再考sizeof我一定會!

大三菜雞一個,在幾場筆試裏面每次都被sizeof虐,這次我不能忍了。

所有代碼演示均在64位系統下

文章參考:百度

定義

sizeof是C/C++中的一個操作符(operator),簡單的說其作用就是返回一個對象或者類型所佔的內存字節數。

語法

sizeof的語法有兩種形式

sizeof(類型)
sizeof 對象

比如

int i;
sizeof(i);//ok
sizeof i;//ok
sizeof(int);//ok
sizeof int;//error
sizeof(2);//2的類型爲int,所以等價於sizeof(int);
sizeof(2+3.14);//3.14的類型爲double,2也會被提升成double類型,所以等價於sizeof(double);

基本數據類型的sizeof 值得一提的是 long在32位和64位的sizeof結果不同

#include <stdio.h>

int main()
{
    printf("%d\n",sizeof(char));//32位結果爲1 64位同
	printf("%d\n",sizeof(short));//32位結果爲2 64位同
	printf("%d\n",sizeof(int));//32位結果爲4 64位同
	printf("%d\n",sizeof(float));//32位結果爲4 64位同
	printf("%d\n",sizeof(long));//32位結果爲4 64位爲8 
	printf("%d\n",sizeof(double));//32位結果爲8 64位同
}

函數方面

#include <stdio.h>
char foo()
{
	printf("foo()hasbeencalled.\n");
	return 'a';
}
int main(void) { 
    char sz=sizeof(foo());
    //foo()的返回值類型爲char,所以sz=sizeof(char),foo()並不會被調用
    printf("sizeof(foo())=%d\n",sz);
}

輸出如下,我們可以發現foo並沒有真正被調用,只會直接拿這個函數的返回類型

sizeof(foo())=1

改變一下

#include <stdio.h>
char foo()
{
	printf("foo()hasbeencalled.\n");
	return 1;
}
void foo1()
{
	printf("foo()1hasbeencalled.\n");
}
int main(void) { 
    printf("sizeof(foo())=%d\n",sizeof(foo()));
    printf("sizeof(foo1())=%d\n",sizeof(foo1()));
}

然後你會發現,不行

#include <stdio.h>
struct S
{
  int f1:1;
  int f2:5;
  int f3:12;
};
int main(void) { 
    int c = sizeof(S);
    printf("%d",c);	// 4
}

改變一下

#include <stdio.h>
struct S
{
  int f1:1;
  int f2:5;
  int f3:12;
};
int main(void) { 
    int c = sizeof(S.f1);
    printf("%d",c); //error
}

這個同樣不行。
以上的原因是因爲C99標準規定,函數、不能確定類型的表達式以及位域(bit-field)成員不能被計算sizeof值

sizeof在指針的應用

指針在C裏面是一個非常重要的概念,它記錄了另一個對象的地址。在32位計算機中,一個指針變量的返回值通常是4(注意結果是以字節爲單位),在64位系統中指針變量的sizeof通常爲8。

#include <stdio.h>

int main()
{
    char *a = "codeMan";
    int *b;
    char **c = &a;
    void(*d)();//函數指針
    printf("%d\n",sizeof(a));//32位結果爲4 64位結果爲8
    printf("%d\n",sizeof(b));//32位結果爲4 64位結果爲8
	printf("%d\n",sizeof(c));//32位結果爲4 64位結果爲8
    printf("%d\n",sizeof(d));//32位結果爲4 64位結果爲8
}

指針變量的sizeof值與指針所指的對象沒有任何關係,正是由於所有的指針變量所佔內存大小相等,所以MFC消息處理函數使用兩個參數WPARAM、LPARAM就能傳遞各種複雜的消息結構(使用指向結構體的指針)。

數組的sizeof

數組的sizeof值等於數組所佔用的內存字節數,跟數組的長度、類型有關

#include <stdio.h>
int main()
{
   	char a[] ="123";
	int b[3];
	double c[3];
	char d[10] ="123";
	printf("%d\n",sizeof(a));	//4
	printf("%d\n",sizeof(b));	//12	=>   4*3
	printf("%d\n",sizeof(c));	//24	=>	 4*4
	printf("%d\n",sizeof(d));	//10 不受內容影響
}

前面我們提到char在32位和64位的sizeof結果均爲1,這裏a不是123麼,怎麼會是長度會是4呢,因爲字符末尾還存在一個’\0’終止符,本身數組長度就是4。

當你在函數裏面傳數組

#include <stdio.h>
void test(char a[3]){//or a[]
	printf("%d\n",sizeof(a));
}
int main(void) { 
   	char a[] ="123";
	test(a);	//64位下輸出8
}

這是因爲在函數調用中,參數的數組只是數組的起始地址罷了,也就是一個指針,前面我們提到32位下指針類型一律爲4,64位一律爲8,所以結果爲8

被虐了無數次的結構體

結構體很繞,爲什麼呢?

因爲存在一種東西叫做字節對齊,有助於加快計算機的取數速度,否則就得多花指令週期了。爲此,編譯器默認會對結構體進行處理(實際上其它地方的數據變量也是如此),讓寬度爲2的基本數據類型(short等)都位於能被2整除的地址上,讓寬度爲4的基本數據類型(int等)都位於能被4整除的地址上,以此類推。這樣,兩個數中間就可能需要加入填充字節,所以整個結構體的sizeof值就增長了。

字節對齊的細節和編譯器實現相關,但一般而言,滿足三個準則:
1)結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2)結構體每個成員相對於結構體首地址的偏移量都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節;
3)結構體的總大小爲結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節。

值得一提的是,這裏所說的“數據寬度”就是指其sizeof的大小。由於結構體的成員可以是複合類型,比如另外一個結構體,所以在尋找最寬基本類型成員時,應當包括複合類型成員的子成員,而不是把複合成員看成是一個整體。但在確定複合類型成員的偏移位置時則是將複合類型作爲整體看待。

還是來一些例子吧

#include <stdio.h>
struct S
{
  int f1;
  int f2;
  int f3;
};
struct S1{
    char c1;
    S s;
    char c2;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 12
    int b = sizeof(S1);
    printf("%d\n",b);	//20
}

結構體S的最寬簡單成員類型爲int,int的sizeof爲4,所以整個sizeof(S)爲4*3=12
結構體S1,雖然裏面有結構體S,但是我們的最簡寬度應該是基本數據類型,所以是int,所以c1和c2補齊各爲4,但是總的S1是要加上S的,所以結果爲20

值得一提的是,空結構體的sizeof不爲0,爲1

#include <stdio.h>
struct S
{

};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 1
}

更虐的結構體含位域

位域啥意思?

有些信息在存儲時,並不需要佔用一個完整的字節,而只需佔幾個或一個二進制位(一個字節個二進制位)。爲了節省存儲空間,並使處理簡便,C語言又提供了一種數據結構,稱爲位域,位域是把一個字節中的二進位劃分爲幾個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。

來個例子

#include <stdio.h>
struct S
{
  int f1:4;		//
  int f2:6;
  int f3:8;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 4
}

分析:

上述中,我們讓f1在一個字節裏面佔4位,f2佔6位,f3佔8位
而一個int會有4個字節,一個字節8位,也就是總共32位,而上述4+6+8<32
所以用一個int存儲足夠,所以sizeof(S)結果爲4

改造一下

#include <stdio.h>
struct S
{
  int f1:16;
  int f2:8;
  int f3:16;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 8
}

分析

依照上面的分析f1和f2可以存在一個int裏面,此時我們還想繼續放f3,發現放不下了,所以我們只能再搞一個int出來,所以是兩個int,所以結果爲8

非位域字段穿插

#include <stdio.h>
struct S
{
  int f1:4;
  int f2;
  int f3:8;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 12
}

分析

非位域字段穿插在其中,不會產生壓縮

總結

  1. 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
  2. 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將從新的存儲單元開始,其偏移量爲其類型大小的整數倍;
  3. 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式,Dev-C++採取壓縮方式;
  4. 如果位域字段之間穿插着非位域字段,則不進行壓縮;
  5. 整個結構體的總大小爲最寬基本類型成員大小的整數倍。

總結

ps:引用自他人

理解 sizeof 只需要抓住一個要點:棧
程序存儲分佈有三個區域:棧、靜態和動態。能夠從代碼直接操作的對象,包括任何類型的變量、指針,都是在棧上的;動態和靜態存儲區是靠棧上的所有指針間接操作的。 sizeof 操作符,計算的是對象在棧上的投影體積;記住這個就很多東西都很清楚了。

char const*static_string="Hello";
//sizeof(static_string)是sizeof一個指針,所以在32bitsystem是4
char stack_string[]="Hello";
//sizeof(stack_string)是sizeof一個數組,所以是6*sizeof(char)
char*string=newchar[6];
strncpy(string,"Hello",6");
//sizeof(string)是sizeof一個指針,所以還是4。
//和第一個不同的是,這個指針指向了動態存儲區而不是靜態存儲區。

好了 下次考sizeof我一定會 如果這篇文章有幫到你 麻煩點個贊 讓我收到你的支持 這是我分享知識的動力 謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章