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