欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語言的結(jié)構(gòu)體你了解嗎

 更新時間:2022年02月21日 09:58:34   作者:c鐵柱同學  
這篇文章主要為大家詳細介紹了C語言的結(jié)構(gòu)體,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

結(jié)構(gòu)體內(nèi)存對齊

當我們創(chuàng)建一個結(jié)構(gòu)體變量時,內(nèi)存就會開辟一塊空間,那么在創(chuàng)建結(jié)構(gòu)體變量時內(nèi)存到底是怎么開辟空間的呢?會開辟多大的空間呢?我們來看一下下面的代碼

struct S
{
	int i;
	char c;
	char b;
};
struct G
{
	
	char c;
	int i;
	char b;
};
int main()
{
	struct S u;
	struct G g;
	printf("%d\n", sizeof(u));
	printf("%d\n", sizeof(g));
	return 0;
}

在這個代碼中,我們創(chuàng)建了兩個結(jié)構(gòu)體類型,并用這兩個類型創(chuàng)建了兩個結(jié)構(gòu)體變量,當變量被創(chuàng)建的時候,內(nèi)存就會為這些變量開辟空間,而在這兩個變量中,我們都創(chuàng)建了兩個char類型的成員和一個int類型的成員,不同的是這三個成員的排列順序不同,然后我們來打印一下這兩個變量所占的字節(jié),來看一下有什么不同

在這里插入圖片描述

我們發(fā)現(xiàn),這兩個結(jié)構(gòu)體變量雖然里面的元素類型一樣,但是他們的大小并不一樣,而他們之間的區(qū)別就是結(jié)構(gòu)體成員的排列順序不同,所以我們可以知道,結(jié)構(gòu)體成員的排列順序是會影響結(jié)構(gòu)體的大小的,那么他到底是怎么影響的呢,這就和結(jié)構(gòu)體的創(chuàng)建規(guī)則有關(guān)。

結(jié)構(gòu)體在創(chuàng)建時要進行內(nèi)存對齊,對齊的規(guī)則是:

1.第一個成員在與結(jié)構(gòu)體變量偏移量為0的地址處。

2.其他成員變量要對齊到某個數(shù)字(對齊數(shù))的整數(shù)倍的地址處。
對齊數(shù) = 編譯器默認的一個對齊數(shù) 與 該成員大小的較小值。
VS中默認的值為8

3.結(jié)構(gòu)體總大小為最大對齊數(shù)(每個成員變量都有一個對齊數(shù))的整數(shù)倍。

4.如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對齊到自己的最大對齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整 體大小就是所有最大對齊數(shù)(含嵌套結(jié)構(gòu)體的對齊數(shù))的整數(shù)倍。

下面我們來根據(jù)對齊規(guī)則來算一個下上面的兩個結(jié)構(gòu)體的大?。?/p>

struct S
{
	int i;
	char c;
	char b;
};

首先,我們先看struct S,第一個成員是int類型,占4個字節(jié),它的對齊數(shù)就是4,第二個是char類型,占一個字節(jié),對齊數(shù)是1,一個放在1的整數(shù)位上,也就是可以接著往下放,那就是在int的4個字節(jié)后面緊跟著來存放,第三個也是char類型,對齊數(shù)也是1,那么也可以接著放在后面,三個成員的最大對齊數(shù)是4,根據(jù)對齊規(guī)則,結(jié)構(gòu)體的大小是最大對齊數(shù)的整數(shù)倍,而我們剛剛算完這三個占的大小已經(jīng)是6個字節(jié)了,所以為了對齊,我們要浪費兩個字節(jié)的空間,使這個結(jié)構(gòu)體的大小為8個字節(jié),是最大對齊數(shù)的2倍。

struct G
{
	
	char c;
	int i;
	char b;
};

我們再來看第二個,這里,我們的第一個元素變成了char類型,占一個字節(jié),但是第二個成員是int類型,對齊數(shù)為4,根據(jù)對齊規(guī)則要對齊到4的整數(shù)倍處,那就只能放在結(jié)構(gòu)體開始儲存的位置往后4個字節(jié)的地址處,加上它本身占4個字節(jié),這時候我們在內(nèi)存中就使用了8個字節(jié),然后在存入第三個成員char類型,對齊數(shù)是1,可以直接在后面存放,最大對齊數(shù)還是4,但是我們已經(jīng)使用了9個字節(jié),所以這時候的大小應該是12個字節(jié),4的3倍。

以上的結(jié)果也是符合剛剛的運行結(jié)果的。

下面我們來看一個結(jié)構(gòu)體里嵌套了一個結(jié)構(gòu)體時的例子:

struct G
{

	char c;
	int i;
	char b;
};
struct S
{
	char a;
	int i;
	struct G c;

};

我們在結(jié)構(gòu)體struct S中嵌套了一個結(jié)構(gòu)體struct G,根據(jù)對齊規(guī)則,我們嵌套的結(jié)構(gòu)體一個對齊到自己的最大對齊數(shù)的整數(shù)倍上,那就是4的整數(shù)倍,而第一個成員是char,第二個是int,int對齊到4的整數(shù)倍上,那前兩個成員就占了8個字節(jié),而struct G正好對齊到int的后面,大小我們剛剛算出來是12個字節(jié),加起來就是20個字節(jié),下面我們來驗證一下

在這里插入圖片描述

與運行結(jié)果相同,那么我們的計算就沒有問題。

那么我們結(jié)構(gòu)體在儲存時為什么要進行內(nèi)存對齊呢?主要有以下兩個原因。

1.平臺原因(移植原因):

不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。

2.性能原因:

數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應該盡可能地在自然邊界上對齊。

原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。

跟主要的可能是第二種原因,為了增加處理器的訪問效率,我們選擇了用浪費空間的方式,來使我們的處理器可以一次性的訪問到我們需要的元素,用空間來換時間。

根據(jù)我們的對齊規(guī)則我們也可以發(fā)現(xiàn),如果我們想要在創(chuàng)建結(jié)構(gòu)體變量時節(jié)省空間,我們應該盡量讓小的成員集中在一起,這樣可以減少空間的浪費,節(jié)省空間。

在規(guī)則中我們看到,計算每個成員的對齊數(shù)時要選擇默認對齊數(shù)與該成員大小的較小值,在vs編譯器中這個默認對齊數(shù)是8,而在有的編譯器中沒有默認對齊數(shù),如gcc編譯器的默認對齊數(shù)就是成員自身的大小,當然,這個默認對齊數(shù)也是可以該的,而我們?nèi)绾蝸硇薷哪J對齊數(shù)呢,我們看下面的代碼

struct H
{
	char c1;
	double d;
	char c2;
};
int main()
{
	struct H h;
	printf("%d\n", sizeof(h));
	return 0;
}

根據(jù)我們的對齊規(guī)則,這個結(jié)構(gòu)體的大小應該是24個字節(jié),我們來運行一下看看結(jié)果

在這里插入圖片描述

如果我們要把它的默認對齊數(shù)改為4,那么我們再來重新計算一下,首先第一個char類型占一個字節(jié),然后double類型占8個字節(jié),但是對齊數(shù)為4,對齊到4的倍數(shù),就可以在第4個字節(jié)的位置開始存儲,這時候前兩個只占12個字節(jié),最后一個char占一個字節(jié),最大對齊數(shù)為4,大小為4的倍數(shù),應該為16,我們來驗證一下

在這里插入圖片描述

根據(jù)運行的結(jié)果我們可以看到,確實是改變了默認對齊數(shù)

修改默認對齊數(shù)的方法就是在結(jié)構(gòu)體類型前加上#pragma pack(n),n表示修改后的默認對齊數(shù)的值(一般都是2的次方數(shù),當改為1時,表示不存在對齊),在結(jié)構(gòu)體類型的后面加上#pragma pack()表示取消修改。

#pragma pack(4)
struct H
{
	char c1;
	double d;
	char c2;
};
#pragma pack()

如上面的代碼,表示我們只把#pragma pack(4)~#pragma pack()之間的結(jié)構(gòu)體類型的默認對齊數(shù)改為了4。

結(jié)構(gòu)體傳參

在學習函數(shù)的時候我們曾經(jīng)學到,函數(shù)在調(diào)用時有兩種方法,一種是傳值調(diào)用,一種是傳址調(diào)用C語言–函數(shù),我們來看下面的代碼。

struct S {
	int data[1000];
	int num;
};

//結(jié)構(gòu)體傳參
void print1(struct S s) 
{
	printf("%d\n", s.num);
}
//結(jié)構(gòu)體地址傳參
void print2(struct S* ps) 
{
	printf("%d\n", ps->num);
}
int main()
{
	struct S s = { {1,2,3,4}, 1000 };
	print1(s); //傳結(jié)構(gòu)體
	print2(&s); //傳地址
	return 0;
}

在這個代碼中,我們的print函數(shù)的目的是打印結(jié)構(gòu)體變量中的一個成員,print1傳參時傳的就是結(jié)構(gòu)體變量的值,print2傳的就是結(jié)構(gòu)體變量的地址,他們的不同點在于傳值的時候,我們的形參是實參的一份臨時拷貝,也就是說,當我把結(jié)構(gòu)體變量的以傳值的方式傳參給函數(shù)時,當我們調(diào)用這個函數(shù),內(nèi)存就會把這個結(jié)構(gòu)體拷貝一份,當我們的結(jié)構(gòu)體變量比較小時還好,但是當這個結(jié)構(gòu)體變量里的成員非常多,占據(jù)的空間非常大時,就會導致系統(tǒng)開銷比較大,性能下降,而如果我們使用傳址的方式,我們一個地址的大小也就4或者8個字節(jié),就沒有上面的問題,所以在結(jié)構(gòu)體傳參時,我們盡量要傳地址,既可以節(jié)省時間,也可以節(jié)省空間。

結(jié)構(gòu)體實現(xiàn)位段

什么是位段

位段的聲明和結(jié)構(gòu)是類似的,有兩個不同:

1.位段的成員必須是 char、int、unsigned int 或signed int 。
2.位段的成員名后邊有一個冒號和一個數(shù)字。

struct A 
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

這里的A就是位段,每個成員后面的數(shù)字代表他們需要的二進制位。

位段在內(nèi)存中的存儲

1.位段的成員可以是 int unsigned int signed int 或者是 char (屬于整形家族)類型

2.位段的空間上是按照需要以4個字節(jié)( int )或者1個字節(jié)( char )的方式來開辟的。

3.位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應該避免使用位段。

我們來舉個例子

struct S 
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

我們來研究這個位段在內(nèi)存中是如何儲存的

首先,我們看成員a,它占3個二進制位,是一個char類型,我們需要創(chuàng)建出一個字節(jié)的空間,也就是8個二進制位,a要走了3個,我們假設在存儲是,我們存儲的順序是與小段存儲的方式類似,也是從右向左存儲的,那么a在內(nèi)存中存儲的位置應該是后面的三個二進制位

低地址->高地址
  ********
       aaa

假設一個代表內(nèi)存中的一個二進制位,這八個代表一個字節(jié),這3個a上面對應的*就是a在這一個字節(jié)中所占的空間。
b需要4個二進制位,而我們剛剛創(chuàng)建的一個字節(jié)中還有5個二進制位,所以我們把b的4個二進制位放在a的后面。

低地址->高地址
  ********
   bbbbaaa

這就是a與b在內(nèi)存中的存儲情況,而這是c需要個二進制位,我們只剩下一個二進制位了,而c又是一個char類型,所以我們需要再創(chuàng)建一個字節(jié)的空間,而當我們創(chuàng)建好了新的空間,c的個二進制位應該怎么儲存呢,我們是接著第一個字節(jié)把剩下的二進制位用完還是在我們新創(chuàng)建的字節(jié)里重新儲存呢,假設內(nèi)存在存儲時選擇直接浪費掉哪個二進制位,在新的空間進行儲存,這時內(nèi)存中的存儲分布應該是這樣

低地址->高地址
  ********  ********
   bbbbaaa     ccccc

存儲d時,d需要4個二進制位,第二個字節(jié)中的二進制位也不夠,所以我們再創(chuàng)建一個字節(jié),存儲d

低地址->高地址
  ********  ********  ********
   bbbbaaa     ccccc      dddd

如果我們的假設沒錯的話,這一個就是a,b,c,d這4個成員在內(nèi)存中的儲存位置,然后我們又對這4個成員進行了賦值

a=10=>1010(二進制數(shù))=>010(3個二進制位)

b=12=>1100(二進制數(shù))=>1100(4個二進制位)

c=3=>11(二進制數(shù))=>00011(3個二進制位)

d=4=>100(二進制數(shù))=>0100(4個二進制位)

當我們用上面的數(shù)據(jù)對我們剛剛的位段進行賦值,那么這個位段在內(nèi)存中存儲的內(nèi)容應該是這樣的

低地址->高地址
  0bbbbaaa  000ccccc  0000dddd
  01100010  00000011  00000100(二進制)
     62        03        04   (十六進制)

根據(jù)我們的計算,我們發(fā)現(xiàn),如果位段按照我們剛剛的假設來存儲,那么在內(nèi)存中存儲的內(nèi)容應該是62 03 03,那我們現(xiàn)在來調(diào)試一下看看

在這里插入圖片描述

結(jié)果與我們推斷的一樣,那么就說明當前的編譯器位段的存儲是按照我們假設的方式來存儲的。

位段的問題

但是位段在C語言中的規(guī)定又有許多不確定的地方,

1.int 位段被當成有符號數(shù)還是無符號數(shù)是不確定的。

2.位段中最大位的數(shù)目不能確定。(16位機器最大16,32位機器最大32,寫成27,在16位機 器會出問題。

3.位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配標準尚未定義。

4.當一個結(jié)構(gòu)包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是 舍棄剩余的位還是利用,這是不確定的。

不同的平臺可能對上述的問題有不同的規(guī)定,所以位段是不能跨平臺的

跟結(jié)構(gòu)相比,位段可以達到同樣的效果,但是可以很好的節(jié)省空間,但是有跨平臺的問題存在。

位段的使用環(huán)境是在我們傳輸一個數(shù)據(jù)包時,可以使用位段使數(shù)據(jù)包在不能壓縮的情況下,所占的空間最小。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!  

相關(guān)文章

最新評論