内存对齐规则总结
由于某些硬件平臺(tái)不能任意訪問(wèn)地址數(shù)據(jù),只能在某些地址處取某些特定類型的數(shù)據(jù);并且處理器訪問(wèn)未對(duì)齊的內(nèi)存時(shí),需要多次讀取并對(duì)多余數(shù)據(jù)進(jìn)行剔除,相較于對(duì)齊內(nèi)存訪問(wèn),耗費(fèi)了更多的時(shí)間,降低了數(shù)據(jù)訪問(wèn)效率,因此需要內(nèi)存對(duì)齊。
一、內(nèi)存字節(jié)對(duì)齊的規(guī)則
1.數(shù)據(jù)類型自然邊界對(duì)齊
char型數(shù)據(jù)自身對(duì)齊值為1字節(jié),short型數(shù)據(jù)為2字節(jié),int/float型為4字節(jié),double型為8字節(jié),long型數(shù)據(jù)為4字節(jié)(32位編譯器)或8字節(jié)(64位編譯器),void*型數(shù)據(jù)為4字節(jié)(32位編譯器)或8字節(jié)(64位編譯器)。
2.結(jié)構(gòu)體、類的自身對(duì)齊
為結(jié)構(gòu)體分配內(nèi)存時(shí),分配的內(nèi)存大小至少是各個(gè)字段的長(zhǎng)度和。通常,分配的結(jié)構(gòu)體的長(zhǎng)度會(huì)大于結(jié)構(gòu)體各個(gè)字段的長(zhǎng)度和,因?yàn)榻Y(jié)構(gòu)體需要對(duì)齊,即結(jié)構(gòu)體各字段之間需要填充。
缺省情況下,編譯器為結(jié)構(gòu)體的每個(gè)成員按其自然邊界對(duì)齊方式分配空間,并按照每個(gè)成員被聲明的順序在內(nèi)存中順序存儲(chǔ),第一個(gè)成員的地址和整個(gè)結(jié)構(gòu)體的地址相同。結(jié)構(gòu)體整體的默認(rèn)字節(jié)對(duì)齊值是結(jié)構(gòu)體中所有成員中對(duì)齊參數(shù)最大值,結(jié)構(gòu)體長(zhǎng)度的計(jì)算必須取所用過(guò)的所有對(duì)齊參數(shù)的整數(shù)倍。
結(jié)構(gòu)體中每個(gè)成員的首地址是成員自身自然邊界對(duì)齊值的整數(shù)倍,如果成員自身大小大于指定對(duì)齊字節(jié),以指定對(duì)齊字節(jié)整數(shù)倍為基準(zhǔn)對(duì)齊;
結(jié)構(gòu)體整體對(duì)齊方式取決于結(jié)構(gòu)體中所有成員的自然邊界對(duì)齊值最大值的整數(shù)倍,如果最大成員大小超過(guò)指定對(duì)齊字節(jié),以指定對(duì)齊字節(jié)整數(shù)倍為基準(zhǔn)對(duì)齊(也叫補(bǔ)齊,目的是多個(gè)結(jié)構(gòu)體連續(xù)存儲(chǔ)時(shí)也滿足對(duì)齊要求)。
3.編譯器指定對(duì)齊
內(nèi)存字節(jié)對(duì)齊是GCC編譯器對(duì)C語(yǔ)言進(jìn)行的擴(kuò)展。在缺省情況下,C編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然邊界對(duì)齊條件分配空間。
(1)GCC編譯器兩種內(nèi)存對(duì)齊的方法
屬性設(shè)置方式(推薦)
__attribute__ ((aligned(n))); //讓所作用的結(jié)構(gòu)體或者類或者聯(lián)合或者一個(gè)類型的變量(對(duì)象)分配地址空間時(shí)的地址在編譯過(guò)程中按n字節(jié)對(duì)齊
__attribute__ ((packed)); //取消結(jié)構(gòu)在編譯過(guò)程中的優(yōu)化對(duì)齊
如果__attribute__((aligned(n)))作用于一個(gè)類型,那么該類型的變量在分配地址空間時(shí),其存放的地址一定按照n字節(jié)對(duì)齊(n必須是2的冪次方);如果類型中的成員的自然邊界對(duì)齊值大于n,則按照機(jī)器字長(zhǎng)(32位或者64位)對(duì)齊。類型占用的空間,即大小,需要是n的整數(shù)倍,以保證在申請(qǐng)連續(xù)存儲(chǔ)空間的時(shí)候,每一個(gè)元素的地址也是按照n字節(jié)對(duì)齊。
32位處理器指定4字節(jié)對(duì)齊方式實(shí)例如下:
struct test{char a[18];double b;char c;int d;short e;
}__attribute__((aligned(4)));//實(shí)際有效指定對(duì)齊值為4字節(jié)(機(jī)器字長(zhǎng))sizeof(struct test);//40字節(jié)
第一個(gè)成員(char a[18]):假設(shè)放在內(nèi)存開始地址為0的位置,占用內(nèi)存地址范圍為0~18。
第二個(gè)成員(double b):double類型占8字節(jié),大于指定對(duì)齊字節(jié)(4字節(jié)),因此以4字節(jié)對(duì)齊為基準(zhǔn)。由于第一個(gè)成員結(jié)束地址為18,不是4的整數(shù)倍,需要再加2個(gè)字節(jié)從地址20開始。
第三個(gè)成員(char c):char類型占1字節(jié),可以直接從第二個(gè)成員結(jié)束地址28開始。
第四個(gè)成員(int d):int類型占4字節(jié),地址29不是4的整數(shù)倍,需要加3個(gè)字節(jié),從地址32開始。
第五個(gè)成員(short e):short類型占2字節(jié),地址36剛好是2的整數(shù)倍,可以直接放在當(dāng)前地址。
最后是對(duì)整個(gè)結(jié)構(gòu)體補(bǔ)齊,該結(jié)構(gòu)體中最大字節(jié)為8字節(jié)(double類型),大于指定對(duì)齊字節(jié),所以還是以4字節(jié)補(bǔ)齊為基準(zhǔn)。整個(gè)結(jié)構(gòu)體結(jié)束地址為38,地址38不是4的整數(shù)倍,需要額外加2個(gè)字節(jié)填充,即整個(gè)結(jié)構(gòu)體的大小為40字節(jié)。具體對(duì)齊結(jié)構(gòu)如下圖所示。
偽指令方式(最多只支持8字節(jié)對(duì)齊,不建議使用)
#pragma pack(n) //n取值可以為1、2、4、8,在編譯過(guò)程中按照n個(gè)字節(jié)對(duì)齊
#pragma pack() //取消指定對(duì)齊,按照編譯器的優(yōu)化對(duì)齊方式對(duì)齊
32位處理器指定4字節(jié)對(duì)齊方式實(shí)例如下:
#pragma pack(4)
struct test{char a;int b;short c;char *p;double d;
};
#pragma pack()sizeof(struct test);//24字節(jié)
(2)其他編譯器內(nèi)存對(duì)齊方式
#if defined (__CC_ARM) /*!< ARM Compiler */ //MDK__align(4) uint16_t data[40];//存儲(chǔ)類修飾符,只修飾最高級(jí)類型對(duì)象,不能用于結(jié)構(gòu)或者函數(shù)對(duì)象//__packed是1字節(jié)對(duì)齊,不使用__packed的話系統(tǒng)以默認(rèn)方式對(duì)齊#elif defined ( __ICCARM__ ) /*!< IAR Compiler */ #pragma data_alignment=4 uint16_t data[40];#elif defined (__GNUC__) /*!< GNU Compiler */ __attribute__ ((aligned (4))) uint16_t data[40]; #elif defined (__TASKING__) /*!< TASKING Compiler */ __align(4) uint16_t data[40];#endif /* __CC_ARM */
二、檢查內(nèi)存地址是否對(duì)齊(4字節(jié)為例)
4字節(jié)對(duì)齊的地址是4(100b)字節(jié)的整數(shù)倍,也就是說(shuō)地址的二進(jìn)制表示形式以00結(jié)尾,因此可以通過(guò)以下方式對(duì)4字節(jié)對(duì)齊地址進(jìn)行測(cè)試:
if((address & 0x3) == 0)
{//The address is 4-byte aligned here
}if(address & 0x3)
{//The address is not 4-byte aligned here
}
總結(jié)
- 上一篇: Java中的List你真的会用吗
- 下一篇: build-helper-maven-p