【嵌入式】C语言高级编程-地址对齐(07)
00. 目錄
文章目錄
- 00. 目錄
- 01. 數(shù)據(jù)對齊概述
- 02. 數(shù)據(jù)需要對齊的原因
- 03. 屬性聲明: aligned
- 04. 結(jié)構(gòu)體對齊
- 05. 附錄
01. 數(shù)據(jù)對齊概述
一般情況下,當(dāng)我們定義一個變量,編譯器會按照默認(rèn)的地址對齊方式,來給該變量分配一個存儲空間地址。如果該變量是一個 int 型數(shù)據(jù),那么編譯器就會按4字節(jié)或4字節(jié)的整數(shù)倍對齊;如果該變量是一個 short 型數(shù)據(jù),那么編譯器就會按2字節(jié)或2字節(jié)的整數(shù)倍邊界對齊;如果是一個 char 類型的變量,那么編譯器就會按照1字節(jié)對齊。
程序示例
#include <stdio.h>int a1 = 1; int a2 = 2; char c1 = 'M'; char c2 = 'F';int main(void) {printf("a1: %p\n", &a1);printf("a2: %p\n", &a2);printf("c1: %p\n", &c1);printf("c2: %p\n", &c2);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out a1: 0x5587a10e2010 a2: 0x5587a10e2014 c1: 0x5587a10e2018 c2: 0x5587a10e2019通過運(yùn)行結(jié)果我們可以看到,對于 int 型數(shù)據(jù),其在內(nèi)存中的地址都是以4字節(jié)或4字節(jié)整數(shù)倍對齊的。而 char 類型的數(shù)據(jù),其在內(nèi)存中是以1字節(jié)對齊的。變量 c2 就直接分配到了 c1 變量的下一個存儲單元,不用像 int 數(shù)據(jù)那樣考慮4字節(jié)對齊。接下來,我們修改一下程序,指定變量 c2 按4字節(jié)對齊。
程序示例
#include <stdio.h>int a1 = 1; int a2 = 2; char c1 = 'M'; char c2 __attribute__((aligned(4))) = 'F';int main(void) {printf("a1: %p\n", &a1);printf("a2: %p\n", &a2);printf("c1: %p\n", &c1);printf("c2: %p\n", &c2);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ ./a.out a1: 0x557fafac8010 a2: 0x557fafac8014 c1: 0x557fafac8018 c2: 0x557fafac801c通過運(yùn)行結(jié)果可以看到,字符變量 c2 由于使用 aligned 屬性聲明按照4字節(jié)邊界對齊,所以編譯器不可能再給其分配 0x00402009 這個地址,因為這個地址不是4字節(jié)對齊的。編譯器空出3個字節(jié)單元,直接從 0x0040200C 這個地址上給變量 c2 分配存儲空間。
02. 數(shù)據(jù)需要對齊的原因
通過 aligned 這個屬性聲明,我們雖然可以顯式指定變量的地址對齊方式,但是也會因邊界對齊造成一定的內(nèi)存空洞,浪費一定的內(nèi)存空間。比如在上面這個程序中,0x557fafac8019~0x557fafac801b這三個地址空間的存儲單元就沒有被使用。
既然地址對齊會造成一定的內(nèi)存空洞,那我們?yōu)槭裁催€要按照這種對齊方式去存儲數(shù)據(jù)呢?一個主要原因就是,這種對齊設(shè)置可以簡化 CPU 和內(nèi)存 RAM 之間的接口和硬件設(shè)計。比如一個32位的計算機(jī)系統(tǒng),CPU 讀取內(nèi)存時,硬件設(shè)計上可能只支持4字節(jié)或4字節(jié)倍數(shù)對齊的地址訪問,CPU 每次往內(nèi)存 RAM 讀寫數(shù)據(jù)時,一個周期可以讀寫4個字節(jié)。如果我們把一個數(shù)據(jù)放在4字節(jié)對齊的地址上,那么CPU一次就可以把數(shù)據(jù)讀寫完畢;如果我們把一個 int 型數(shù)據(jù)放在一個非4字節(jié)對齊的地址上,那 CPU 就要分2次才能把這個4字節(jié)大小的數(shù)據(jù)讀寫完畢。
為了配合計算機(jī)的硬件設(shè)計,編譯器在編譯程序時,對于一些基本數(shù)據(jù)類型,比如 int、char、short、float 等,會按照其數(shù)據(jù)類型的大小進(jìn)行地址對齊,按照這種地址對齊方式分配的存儲地址,CPU 一次就可以讀寫完畢。雖然邊界對齊會造成一些內(nèi)存空洞,浪費一些內(nèi)存單元,但是在硬件上的設(shè)計卻大大簡化了。這也是編譯器給我們定義的變量分配地址時,不同類型變量按不同字節(jié)數(shù)地址對齊的原因。
除了 int、char、short、float 這些基本類型數(shù)據(jù),對于一些復(fù)合類型數(shù)據(jù),也要滿足地址對齊要求。
03. 屬性聲明: aligned
GNU C 通過 attribute 來聲明 aligned 和 packed 屬性,指定一個變量或類型的對齊方式。這兩個屬性用來告訴編譯器:在給變量分配存儲空間時,要按指定的地址對齊方式給變量分配地址。如果你想定義一個變量,在內(nèi)存中以8字節(jié)地址對齊,就可以這樣定義。
int var __attribute__((aligned(8));通過 aligned 屬性,我們可以直接顯式指定變量 var 在內(nèi)存中的地址對齊方式。aligned 有一個參數(shù),表示要按幾字節(jié)對齊,使用時要注意地址對齊的字節(jié)數(shù)必須是2的冪次方,否則編譯就會出錯。
錯誤示例
#include <stdio.h>int main(void) {int var __attribute__((aligned(3)));var = 3;return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c test.c: In function ‘main’: test.c:5:5: error: requested alignment ‘3’ is not a positive power of 25 | int var __attribute__((aligned(3)));| ^~~04. 結(jié)構(gòu)體對齊
結(jié)構(gòu)體作為一種復(fù)合數(shù)據(jù)類型,編譯器在給一個結(jié)構(gòu)體變量分配存儲空間時,不僅要考慮結(jié)構(gòu)體內(nèi)各個基本成員的地址對齊,還要考慮結(jié)構(gòu)體整體的對齊。為了結(jié)構(gòu)體內(nèi)的成員地址對齊,編譯器可能會在結(jié)構(gòu)體內(nèi)填充一些空間;為了結(jié)構(gòu)體整體對齊,編譯器可能會在結(jié)構(gòu)體的末尾填充一些空間。
接下來,我們定義一個結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)定義 int、char 和 short 三種成員,并打印結(jié)構(gòu)體的大小和各個成員的地址。
程序示例
#include <stdio.h>struct student {char a;int b;short c; };int main(void) {struct student s;printf("sizeof(s): %lu\n", sizeof(s));printf("a: %p\n", &s.a);printf("b: %p\n", &s.b);printf("c: %p\n", &s.c);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out sizeof(s): 12 a: 0x7ffdc37394bc b: 0x7ffdc37394c0 c: 0x7ffdc37394c4我們可以看到,因為結(jié)構(gòu)體的成員 b 需要4字節(jié)對齊,編譯器在給成員 a 分配完空間后,接著會空出3個字節(jié),在滿足4字節(jié)對齊的 0x7ffdc37394c0地址處才給成員 b 分配存儲空間。接著是 short 類型的成員 c 占據(jù)2字節(jié)的存儲空間。三個結(jié)構(gòu)體成員一共占據(jù)4+4+2=10字節(jié)的存儲空間,根據(jù)結(jié)構(gòu)體的對齊規(guī)則,結(jié)構(gòu)體的整體對齊要向結(jié)構(gòu)體所有成員中最大對齊字節(jié)數(shù)或其整數(shù)倍對齊,或者說結(jié)構(gòu)體的整體長度要為其最大成員字節(jié)數(shù)的整數(shù)倍,如果不是整數(shù)倍要補(bǔ)齊。因為結(jié)構(gòu)體最大成員 int 為4個字節(jié),或者說按4字節(jié)的整數(shù)倍對齊,所以結(jié)構(gòu)體的長度要為4的整數(shù)倍,要在結(jié)構(gòu)體的末尾補(bǔ)充2個字節(jié),所以最后結(jié)構(gòu)體的 size 為12個字節(jié)。
結(jié)構(gòu)體成員中,不同的排放順序,可能也會導(dǎo)致結(jié)構(gòu)體的整體長度不一樣,我們修改一下上面的程序。
程序示例
#include <stdio.h>struct student {char a;short b;int c; };int main(void) {struct student s;printf("sizeof(s): %lu\n", sizeof(s));printf("a: %p\n", &s.a);printf("b: %p\n", &s.b);printf("c: %p\n", &s.c);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out sizeof(s): 8 a: 0x7ffd7f045c60 b: 0x7ffd7f045c62 c: 0x7ffd7f045c64我們調(diào)整了一些成員順序,你會發(fā)現(xiàn),char 型變量 a 和 short 型變量 b,分配在了結(jié)構(gòu)體的前4個字節(jié)存儲空間中,而且都滿足各自的地址對齊,整個結(jié)構(gòu)體大小是8字節(jié),只造成一個字節(jié)的內(nèi)存空洞。我們繼續(xù)修改程序,讓 short 型的變量 b 按4字節(jié)對齊:
程序示例
#include <stdio.h>struct student {char a;short b __attribute__((aligned(4)));int c; };int main(void) {struct student s;printf("sizeof(s): %lu\n", sizeof(s));printf("a: %p\n", &s.a);printf("b: %p\n", &s.b);printf("c: %p\n", &s.c);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out sizeof(s): 12 a: 0x7ffc5dc269dc b: 0x7ffc5dc269e0 c: 0x7ffc5dc269e4你會發(fā)現(xiàn),結(jié)構(gòu)體的大小又重新變?yōu)?2個字節(jié)。這是因為,我們顯式指定 short 變量以4字節(jié)地址對齊,導(dǎo)致變量 a 的后面填充了3個字節(jié)空間。int 型變量 c 也要4字節(jié)對齊,所以變量 b 的后面也填充了2個字節(jié),導(dǎo)致整個結(jié)構(gòu)體的大小為12字節(jié)。
我們不僅可以顯式指定結(jié)構(gòu)體內(nèi)某個成員的地址對齊,也可以指定整個結(jié)構(gòu)體的對齊方式。
程序示例
#include <stdio.h>struct student {char a;short b;int c; } __attribute__((aligned(16)));int main(void) {struct student s;printf("sizeof(s): %lu\n", sizeof(s));printf("a: %p\n", &s.a);printf("b: %p\n", &s.b);printf("c: %p\n", &s.c);return 0; }執(zhí)行結(jié)果
deng@itcast:~/tmp$ gcc test.c deng@itcast:~/tmp$ ./a.out sizeof(s): 16 a: 0x7ffe81c5e530 b: 0x7ffe81c5e532 c: 0x7ffe81c5e534在這個結(jié)構(gòu)體中,各個成員一共占8個字節(jié)。通過前面學(xué)習(xí)我們知道,整個結(jié)構(gòu)體的對齊只要是最大成員對齊字節(jié)數(shù)的整數(shù)倍即可。所以這個結(jié)構(gòu)體整體就以8字節(jié)對齊,結(jié)構(gòu)體的整體長度為8字節(jié)。但是我們在這里,顯式指定結(jié)構(gòu)體整體以16字節(jié)對齊,所以編譯器就會在這個結(jié)構(gòu)體的末尾填充8個字節(jié)以滿足16字節(jié)對齊的要求,導(dǎo)致結(jié)構(gòu)體的總長度變?yōu)?6字節(jié)。
05. 附錄
參考:C語言嵌入式Linux高級編程
總結(jié)
以上是生活随笔為你收集整理的【嵌入式】C语言高级编程-地址对齐(07)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【嵌入式】C语言高级编程-长度为0的数组
- 下一篇: 【嵌入式】C语言高级编程-变参函数(08