GNU C 通過 attribute 來聲明 aligned 和 packed 屬性,指定一個(gè)變量或類型的對(duì)齊方式。這兩個(gè)屬性用來告訴編譯器:在給變量分配存儲(chǔ)空間時(shí),要按指定的地址對(duì)齊方式給變量分配地址。如果你想定義一個(gè)變量,在內(nèi)存中以8字節(jié)地址對(duì)齊,就可以這樣定義。
雙城ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來市場(chǎng)廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:13518219792(備注:SSL證書合作)期待與您的合作!int a __attribute__((aligned(8));
通過 aligned 屬性,我們可以直接顯式指定變量 a 在內(nèi)存中的地址對(duì)齊方式。aligned 有一個(gè)參數(shù),表示要按幾字節(jié)對(duì)齊,使用時(shí)要注意地址對(duì)齊的字節(jié)數(shù)必須是2的冪次方,否則編譯就會(huì)出錯(cuò)。
一般情況下,當(dāng)我們定義一個(gè)變量,編譯器會(huì)按照默認(rèn)的地址對(duì)齊方式,來給該變量分配一個(gè)存儲(chǔ)空間地址。如果該變量是一個(gè) int 型數(shù)據(jù),那么編譯器就會(huì)按4字節(jié)或4字節(jié)的整數(shù)倍對(duì)齊;如果該變量是一個(gè) short 型數(shù)據(jù),那么編譯器就會(huì)按2字節(jié)或2字節(jié)的整數(shù)倍邊界對(duì)齊;如果是一個(gè) char 類型的變量,那么編譯器就會(huì)按照1字節(jié)對(duì)齊。
int a = 1;
int b = 2;
char c1 = 3;
char c2 = 4;
int main(void)
{
printf("a: %p\n",&a);
printf("b: %p\n",&b);
printf("c1:%p\n",&c1);
printf("c2:%p\n",&c2);
return 0;
}
在上面的程序中,我們分別定義2個(gè) int 型變量,2個(gè) char 型變量,然后分別打印它們的地址,運(yùn)行結(jié)果如下。
a: 00402000
b: 00402004
c1: 00402008
c2: 00402009
通過運(yùn)行結(jié)果我們可以看到,對(duì)于 int 型數(shù)據(jù),其在內(nèi)存中的地址都是以4字節(jié)或4字節(jié)整數(shù)倍對(duì)齊的。而 char 類型的數(shù)據(jù),其在內(nèi)存中是以1字節(jié)對(duì)齊的。變量 c2 就直接分配到了 c1 變量的下一個(gè)存儲(chǔ)單元,不用像 int 數(shù)據(jù)那樣考慮4字節(jié)對(duì)齊。接下來,我們修改一下程序,指定變量 c2 按4字節(jié)對(duì)齊。
int a = 1;
int b = 2;
char c1 = 3;
char c2 __attribute__((aligned(4))) = 4;
int main(void)
{
printf("a: %p\n",&a);
printf("b: %p\n",&b);
printf("c1:%p\n",&c1);
printf("c2:%p\n",&c2);
return 0;
}
運(yùn)行結(jié)果如下。
a: 00402000
b: 00402004
c1: 00402008
c2: 0040200C
通過運(yùn)行結(jié)果可以看到,字符變量 c2 由于使用 aligned 屬性聲明按照4字節(jié)邊界對(duì)齊,所以編譯器不可能再給其分配 0x00402009 這個(gè)地址,因?yàn)檫@個(gè)地址不是4字節(jié)對(duì)齊的。編譯器空出3個(gè)字節(jié)單元,直接從 0x0040200C 這個(gè)地址上給變量 c2 分配存儲(chǔ)空間。
通過 aligned 這個(gè)屬性聲明,我們雖然可以顯式指定變量的地址對(duì)齊方式,但是也會(huì)因邊界對(duì)齊造成一定的內(nèi)存空洞,浪費(fèi)一定的內(nèi)存空間。比如在上面這個(gè)程序中,0x00402009~0x0040200b 這三個(gè)地址空間的存儲(chǔ)單元就沒有被使用。
既然地址對(duì)齊會(huì)造成一定的內(nèi)存空洞,那我們?yōu)槭裁催€要按照這種對(duì)齊方式去存儲(chǔ)數(shù)據(jù)呢?一個(gè)主要原因就是,這種對(duì)齊設(shè)置可以簡(jiǎn)化 CPU 和內(nèi)存 RAM 之間的接口和硬件設(shè)計(jì)。比如一個(gè)32位的計(jì)算機(jī)系統(tǒng),CPU 讀取內(nèi)存時(shí),硬件設(shè)計(jì)上可能只支持4字節(jié)或4字節(jié)倍數(shù)對(duì)齊的地址訪問,CPU 每次往內(nèi)存 RAM 讀寫數(shù)據(jù)時(shí),一個(gè)周期可以讀寫4個(gè)字節(jié)。如果我們把一個(gè)數(shù)據(jù)放在4字節(jié)對(duì)齊的地址上,那么CPU一次就可以把數(shù)據(jù)讀寫完畢;如果我們把一個(gè) int 型數(shù)據(jù)放在一個(gè)非4字節(jié)對(duì)齊的地址上,那 CPU 就要分2次才能把這個(gè)4字節(jié)大小的數(shù)據(jù)讀寫完畢。
為了配合計(jì)算機(jī)的硬件設(shè)計(jì),編譯器在編譯程序時(shí),對(duì)于一些基本數(shù)據(jù)類型,比如 int、char、short、float 等,會(huì)按照其數(shù)據(jù)類型的大小進(jìn)行地址對(duì)齊,按照這種地址對(duì)齊方式分配的存儲(chǔ)地址,CPU 一次就可以讀寫完畢。雖然邊界對(duì)齊會(huì)造成一些內(nèi)存空洞,浪費(fèi)一些內(nèi)存單元,但是在硬件上的設(shè)計(jì)卻大大簡(jiǎn)化了。這也是編譯器給我們定義的變量分配地址時(shí),不同類型變量按不同字節(jié)數(shù)地址對(duì)齊的原因。
除了 int、char、short、float 這些基本類型數(shù)據(jù),對(duì)于一些復(fù)合類型數(shù)據(jù),也要滿足地址對(duì)齊要求。
結(jié)構(gòu)體作為一種復(fù)合數(shù)據(jù)類型,編譯器在給一個(gè)結(jié)構(gòu)體變量分配存儲(chǔ)空間時(shí),不僅要考慮結(jié)構(gòu)體內(nèi)各個(gè)基本成員的地址對(duì)齊,還要考慮結(jié)構(gòu)體整體的對(duì)齊。為了結(jié)構(gòu)體內(nèi)的成員地址對(duì)齊,編譯器可能會(huì)在結(jié)構(gòu)體內(nèi)填充一些空間;為了結(jié)構(gòu)體整體對(duì)齊,編譯器可能會(huì)在結(jié)構(gòu)體的末尾填充一些空間。
接下來,我們定義一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)定義 int、char 和 short 三種成員,并打印結(jié)構(gòu)體的大小和各個(gè)成員的地址。
struct data{
char a;
int b ;
short c ;
}
int main(void)
{
struct data s;
printf("size:%d\n",sizeof(s));
printf("a:%p\n",&s.a);
printf("b:%p\n",&s.b);
printf("c:%p\n",&s.c);
}
程序運(yùn)行結(jié)果如下。
size: 12
&s.a: 0028FF30
&s.b: 0028FF34
&s.c: 0028FF38
我們可以看到,因?yàn)榻Y(jié)構(gòu)體的成員 b 需要4字節(jié)對(duì)齊,編譯器在給成員 a 分配完空間后,接著會(huì)空出3個(gè)字節(jié),在滿足4字節(jié)對(duì)齊的 0x0028FF34 地址處才給成員 b 分配存儲(chǔ)空間。接著是 short 類型的成員 c 占據(jù)2字節(jié)的存儲(chǔ)空間。三個(gè)結(jié)構(gòu)體成員一共占據(jù)4+4+2=10字節(jié)的存儲(chǔ)空間,根據(jù)結(jié)構(gòu)體的對(duì)齊規(guī)則,結(jié)構(gòu)體的整體對(duì)齊要向結(jié)構(gòu)體所有成員中大對(duì)齊字節(jié)數(shù)或其整數(shù)倍對(duì)齊,或者說結(jié)構(gòu)體的整體長(zhǎng)度要為其大成員字節(jié)數(shù)的整數(shù)倍,如果不是整數(shù)倍要補(bǔ)齊。因?yàn)榻Y(jié)構(gòu)體大成員 int 為4個(gè)字節(jié),或者說按4字節(jié)的整數(shù)倍對(duì)齊,所以結(jié)構(gòu)體的長(zhǎng)度要為4的整數(shù)倍,要在結(jié)構(gòu)體的末尾補(bǔ)充2個(gè)字節(jié),所以最后結(jié)構(gòu)體的 size 為12個(gè)字節(jié)。
結(jié)構(gòu)體成員中,不同的排放順序,可能也會(huì)導(dǎo)致結(jié)構(gòu)體的整體長(zhǎng)度不一樣,我們修改一下上面的程序。
struct data{
char a;
short b ;
int c ;
};
int main(void)
{
struct data s;
printf("size: %d\n",sizeof(s));
printf("&s.a: %p\n",&s.a);
printf("&s.b: %p\n",&s.b);
printf("&s.c: %p\n",&s.c);
}
程序運(yùn)行結(jié)果如下。
size: 8
&s.a: 0028FF30
&s.b: 0028FF32
&s.c: 0028FF34
我們調(diào)整了一些成員順序,你會(huì)發(fā)現(xiàn),char 型變量 a 和 short 型變量 b,分配在了結(jié)構(gòu)體的前4個(gè)字節(jié)存儲(chǔ)空間中,而且都滿足各自的地址對(duì)齊,整個(gè)結(jié)構(gòu)體大小是8字節(jié),只造成一個(gè)字節(jié)的內(nèi)存空洞。我們繼續(xù)修改程序,讓 short 型的變量 b 按4字節(jié)對(duì)齊:
struct data{
char a;
short b __attribute__((aligned(4)));
int c ;
};
程序運(yùn)行結(jié)果如下。
size: 12
&s.a: 0028FF30
&s.b: 0028FF34
&s.c: 0028FF38
你會(huì)發(fā)現(xiàn),結(jié)構(gòu)體的大小又重新變?yōu)?2個(gè)字節(jié)。這是因?yàn)椋覀冿@式指定 short 變量以4字節(jié)地址對(duì)齊,導(dǎo)致變量 a 的后面填充了3個(gè)字節(jié)空間。int 型變量 c 也要4字節(jié)對(duì)齊,所以變量 b 的后面也填充了2個(gè)字節(jié),導(dǎo)致整個(gè)結(jié)構(gòu)體的大小為12字節(jié)。
我們不僅可以顯式指定結(jié)構(gòu)體內(nèi)某個(gè)成員的地址對(duì)齊,也可以指定整個(gè)結(jié)構(gòu)體的對(duì)齊方式。
struct data{
char a;
short b;
int c ;
}__attribute__((aligned(16)));
程序運(yùn)行結(jié)果如下。
size: 16
&s.a: 0028FF30
&s.b: 0028FF32
&s.c: 0028FF34
在這個(gè)結(jié)構(gòu)體中,各個(gè)成員一共占8個(gè)字節(jié)。通過前面學(xué)習(xí)我們知道,整個(gè)結(jié)構(gòu)體的對(duì)齊只要是大成員對(duì)齊字節(jié)數(shù)的整數(shù)倍即可。所以這個(gè)結(jié)構(gòu)體整體就以8字節(jié)對(duì)齊,結(jié)構(gòu)體的整體長(zhǎng)度為8字節(jié)。但是我們?cè)谶@里,顯式指定結(jié)構(gòu)體整體以16字節(jié)對(duì)齊,所以編譯器就會(huì)在這個(gè)結(jié)構(gòu)體的末尾填充8個(gè)字節(jié)以滿足16字節(jié)對(duì)齊的要求,導(dǎo)致結(jié)構(gòu)體的總長(zhǎng)度變?yōu)?6字節(jié)。
通過 aligned 屬性,我們可以顯式指定一個(gè)變量的對(duì)齊方式,那么,編譯器就一定會(huì)按照我們指定的大小對(duì)齊嗎?非也!
我們通過這個(gè)屬性聲明,其實(shí)只是建議編譯器按照這種大小地址對(duì)齊,但不能超過編譯器允許的大值。一個(gè)編譯器,對(duì)每個(gè)基本數(shù)據(jù)類型,都有默認(rèn)的大邊界對(duì)齊字節(jié)數(shù)。如果你超過了,不好意思,我不奉陪,編譯器只能按照它規(guī)定的大對(duì)齊來給你的變量分配地址。
char c1 = 3;
char c2 __attribute__((aligned(16))) = 4 ;
int main(void)
{
printf("c1:%p\n",&c1);
printf("c2:%p\n",&c2);
return 0;
}
在這個(gè)程序中,我們指定 char 型的變量 c2 以16字節(jié)對(duì)齊,然后運(yùn)行結(jié)果為:
c1:00402000
c2:00402010
我們可以看到,編譯器給 c2 分配的地址就是16字節(jié)地址對(duì)齊的,如果我們繼續(xù)修改 c2 變量按32字節(jié)對(duì)齊,你會(huì)發(fā)現(xiàn)程序的運(yùn)行結(jié)果不再會(huì)有變化,編譯器還會(huì)分配一個(gè)16字節(jié)對(duì)齊的地址,因?yàn)橐呀?jīng)超過編譯器允許的大值了。
aligned 屬性一般用來增大變量的地址對(duì)齊,元素之間因?yàn)榈刂穼?duì)齊會(huì)造成一定的內(nèi)存空洞。而 packed 屬性則與之相反,用來減少地址對(duì)齊,用來指定變量或類型使用最可能小的地址對(duì)齊方式。
struct data{
char a;
short b __attribute__((packed));
int c __attribute__((packed));
};
int main(void)
{
struct data s;
printf("size: %d\n",sizeof(s));
printf("&s.a: %p\n",&s.a);
printf("&s.b: %p\n",&s.b);
printf("&s.c: %p\n",&s.c);
}
在這個(gè)程序中,我們將結(jié)構(gòu)體的成員 b 和 c 使用 packed 屬性聲明,就是告訴編譯器,盡量使用最可能小的地址對(duì)齊給它們分配地址,盡可能地減少內(nèi)存空洞。程序的運(yùn)行結(jié)果如下。
size: 7
&s.a: 0028FF30
&s.b: 0028FF31
&s.c: 0028FF33
通過結(jié)果我們看到,結(jié)構(gòu)體內(nèi)各個(gè)成員地址的分配,使用最小1字節(jié)的對(duì)齊方式,導(dǎo)致整個(gè)結(jié)構(gòu)體的大小只有7個(gè)字節(jié)。
這個(gè)特性在底層驅(qū)動(dòng)開發(fā)中還是非常有用的。比如,你想定義一個(gè)結(jié)構(gòu)體,封裝一個(gè) IP 控制器的各種寄存器。在 ARM 芯片中,每一個(gè)控制器的寄存器地址空間一般是連續(xù)存在的。如果考慮數(shù)據(jù)對(duì)齊,結(jié)構(gòu)體內(nèi)有空洞,這樣就跟實(shí)際連續(xù)的寄存器地址不一致了,使用 packed 就可以避免這個(gè)問題,結(jié)構(gòu)體的每個(gè)成員都緊挨著依次分配存儲(chǔ)地址,這樣就避免了各個(gè)成員元素因地址對(duì)齊而造成的內(nèi)存空洞。
struct data{
char a;
short b ;
int c ;
}__attribute__((packed));
我們對(duì)整個(gè)結(jié)構(gòu)體添加 packed 屬性,和分別對(duì)每個(gè)成員添加 packed 屬性,效果是一樣的。修改結(jié)構(gòu)體后,程序的運(yùn)行結(jié)果跟上面程序運(yùn)行結(jié)果相同——結(jié)構(gòu)體的大小為7,結(jié)構(gòu)體內(nèi)各成員地址相同。
在 Linux 內(nèi)核中,我們經(jīng)??吹?aligned 和 packed 一起使用,即對(duì)一個(gè)變量或類型同時(shí)使用 aligned 和 packed 屬性聲明。這樣做的好處是,既避免了結(jié)構(gòu)體內(nèi)因地址對(duì)齊產(chǎn)生的內(nèi)存空洞,又指定了整個(gè)結(jié)構(gòu)體的對(duì)齊方式。
struct data{
char a;
short b ;
int c ;
}__attribute__((packed,aligned(8)));
int main(void)
{
struct data s;
printf("size: %d\n",sizeof(s));
printf("&s.a: %p\n",&s.a);
printf("&s.b: %p\n",&s.b);
printf("&s.c: %p\n",&s.c);
}
程序運(yùn)行結(jié)果如下。
size: 8
&s.a: 0028FF30
&s.b: 0028FF31
&s.c: 0028FF33
在這個(gè)程序中,結(jié)構(gòu)體 data 雖然使用 packed 屬性聲明,整個(gè)長(zhǎng)度變?yōu)?,但是我們同時(shí)又使用了 aligned(8) 指定其按8字節(jié)地址對(duì)齊,所以編譯器要在結(jié)構(gòu)體后面填充1個(gè)字節(jié),這樣整個(gè)結(jié)構(gòu)體的大小就變?yōu)?字節(jié),按8字節(jié)地址對(duì)齊。
本教程根據(jù) C語言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
當(dāng)前標(biāo)題:嵌入式C語言自我修養(yǎng)07:地址對(duì)齊那些事兒-創(chuàng)新互聯(lián)
文章路徑:http://aaarwkj.com/article8/ccheip.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、建站公司、網(wǎng)站排名、網(wǎng)站內(nèi)鏈、面包屑導(dǎo)航、用戶體驗(yàn)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容