C語言結(jié)構(gòu)體內(nèi)存對齊問題小結(jié)
1.結(jié)構(gòu)體內(nèi)存對齊
我們已經(jīng)基本掌握了結(jié)構(gòu)體的使用了。那我們現(xiàn)在必須得知道結(jié)構(gòu)體在內(nèi)存中是如何存儲的?內(nèi)存是如何分配的?所以我們得知道如何計算結(jié)構(gòu)體的大小?這就引出了我們今天所要探討的內(nèi)容:結(jié)構(gòu)體內(nèi)存對齊。
1.1 對齊規(guī)則
首先得掌握結(jié)構(gòu)體的對齊規(guī)則:
1. 結(jié)構(gòu)體的第?個成員對?到和結(jié)構(gòu)體變量起始位置偏移量為0的地址處。
2. 其他成員變量要對?到某個數(shù)字(對?數(shù))的整數(shù)倍的地址處。
對齊數(shù) = 編譯器默認的?個對?數(shù) 與 該成員變量大小的 較?值。
- VS 中默認對齊數(shù)的值為 8
- Linux中 gcc 沒有默認對?數(shù),對?數(shù)就是成員??的大小
3. 結(jié)構(gòu)體總大小為最?對?數(shù)(結(jié)構(gòu)體中每個成員變量都有?個對?數(shù),所有對?數(shù)中最?的)的
整數(shù)倍。
4. 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體成員對?到??的成員中最?對?數(shù)的整數(shù)倍處,結(jié)構(gòu)
體的整體??就是所有最?對?數(shù)(含嵌套結(jié)構(gòu)體中成員的對?數(shù))的整數(shù)倍。
范例1:
//范例1
struct S1
{
char c1;//1 8 1
int i; //4 8 4
char c2;//1 8 1
};
int main()
{
struct S1 s1 = { 0 };
printf("%zd\n", sizeof(s1));
return 0;
}我們畫圖分析一下:

我們運行一下結(jié)果看看,是不是12個字節(jié):

確實是12個字節(jié),這就說明,結(jié)構(gòu)體在內(nèi)存存儲中,存在內(nèi)存對齊的原則。
范例2:
//范例2
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
struct S2 s2 = { 0 };
printf("%zd\n", sizeof(s2));
return 0;
}同樣的道理:

運行結(jié)果:

范例3:
//范例3
struct S3
{
double d;//8 8 8
char c; //1 8 1
int i; //4 8 4
};
int main()
{
struct S3 s3 = { 0 };
printf("%zd\n", sizeof(s3));
return 0;
}
運行結(jié)果:

范例4:
//范例4
struct S3
{
double d;//8 8 8
char c; //1 8 1
int i; //4 8 4
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
struct S4 s4 = { 0 };
printf("%zd\n", sizeof(s4));
return 0;
}
運行結(jié)果:

1.2 為什么存在內(nèi)存對齊?
?部分的參考資料都是這樣說的:
1. 平臺原因 (移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2.性能原因:
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在?然邊界上對?。原因在于,為了訪問未對?的內(nèi)存,處理器需要作兩次內(nèi)存訪問;?對?的內(nèi)存訪問僅需要?次訪問。假設(shè)?個處理器總是從內(nèi)存中取8個字節(jié),則地 址必須是8的倍數(shù)。如果我們能保證將所有的double類型的數(shù)據(jù)的地址都對?成8的倍數(shù),那么就可以??個內(nèi)存操作來讀或者寫值了。否則,我們可能需要執(zhí)?兩次內(nèi)存訪問,因為對象可能被分放在兩個8字節(jié)內(nèi)存塊中。
總體來說:結(jié)構(gòu)體的內(nèi)存對?是拿空間來換取時間的做法。
那在設(shè)計結(jié)構(gòu)體的時候,我們既要滿?對?,?要節(jié)省空間,如何做到:
讓占?空間?的成員盡量集中在?起
//例如:
struct S1
{
char c1;//1 8 1
int i; //4 8 4
char c2;//1 8 1
};
//sizeof(struct S1) -> 12個字節(jié)
struct S2
{
char c1;//1 8 1
char c2;//1 8 1
int i; //4 8 4
};
//sizeof(struct S2) -> 8個字節(jié)1.3 修改默認對齊數(shù) #pragma 這個預(yù)處理指令,可以改變編譯器的默認對齊數(shù)。
#include <stdio.h>
#pragma pack(1)//設(shè)置默認對?數(shù)為1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的對?數(shù),還原為默認
int main()
{
//輸出的結(jié)果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}結(jié)構(gòu)體在對齊方式不合適的時候,我們可以自己更改默認對齊數(shù)。
運行結(jié)果:

2.結(jié)構(gòu)體傳參
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//結(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()
{
print1(s); //傳結(jié)構(gòu)體
print2(&s); //傳地址
return 0;
}上?的 print1 和 print2 函數(shù)哪個好些?
答案是:首選print2函數(shù)。
原因:
函數(shù)傳參的時候,參數(shù)是需要壓棧,會有時間和空間上的系統(tǒng)開銷。
如果傳遞?個結(jié)構(gòu)體對象的時候,結(jié)構(gòu)體過?,參數(shù)壓棧的的系統(tǒng)開銷?較?,所以會導(dǎo)致性能的下降。
結(jié)論:
結(jié)構(gòu)體傳參的時候,要傳結(jié)構(gòu)體的地址。
3.結(jié)構(gòu)體實現(xiàn)位段
結(jié)構(gòu)體講完就得講講結(jié)構(gòu)體實現(xiàn)位段的能力。
3.1 什么是位段
位段的聲明和結(jié)構(gòu)是類似的,有兩個不同:
1. 位段的成員必須是 int、unsigned int 或signed int ,在C99中位段成員的類型也可以
選擇其他類型。
2. 位段的成員名后邊有?個冒號和?個數(shù)字。
比如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};A就是?個位段類型。 那位段A所占內(nèi)存的大小是多少?
printf("%d\n", sizeof(struct A));3.2 位段的內(nèi)存分配
1. 位段的成員可以是 int 、 unsigned int 、 signed int 或者是 char 等類型
2. 位段的空間上是按照需要以4個字節(jié)( int )或者1個字節(jié)( char )的?式來開辟的。
3. 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應(yīng)該避免使?位段。
//?個例?
#include <stdio.h>
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;
}
3.3 位段的跨平臺問題
1. int 位段被當成有符號數(shù)還是?符號數(shù)是不確定的。
2. 位段中最?位的數(shù)目不能確定。(16位機器最大16,32位機器最大32,寫成27,在16位機器會
出問題。
3. 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配,標準尚未定義。
4. 當?個結(jié)構(gòu)包含兩個位段,第?個位段成員?較大,?法容納于第?個位段剩余的位時,是舍棄
剩余的位還是利?,這是不確定的。
總結(jié):
跟結(jié)構(gòu)相?,位段可以達到同樣的效果,并且可以很好的節(jié)省空間,但是有跨平臺的問題存在。
3.4 位段使用的注意事項
位段的?個成員共有同?個字節(jié),這樣有些成員的起始位置并不是某個字節(jié)的起始位置,那么這些位置處是沒有地址的。內(nèi)存中每個字節(jié)分配?個地址,?個字節(jié)內(nèi)部的bit位是沒有地址的。 所以不能對位段的成員使?&操作符,這樣就不能使?scanf直接給位段的成員輸?值,只能是先輸?放在?個變量中,然后賦值給位段的成員。
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//這是錯誤的
//正確的?范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}到此這篇關(guān)于C語言結(jié)構(gòu)體內(nèi)存對齊問題的文章就介紹到這了,更多相關(guān)C語言結(jié)構(gòu)體內(nèi)存對齊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jQuery移動頁面開發(fā)中主題按鈕的設(shè)計示例
這篇文章主要介紹了jQuery移動頁面開發(fā)中主題按鈕的設(shè)計示例,jQuery是當今最具人氣的JavaScript開發(fā)類庫,需要的朋友可以參考下2015-12-12
vscode ssh遠程連接服務(wù)器一直卡在下載 vscode server問題解決
在使用vscode使用ssh遠程連接服務(wù)器時,一直卡在下載"vscode 服務(wù)器"階段,本文主要介紹了vscode ssh遠程連接服務(wù)器一直卡在下載 vscode server問題解決,感興趣的可以了解一下2025-01-01

