內(nèi)存對齊
概念:
百度百科內(nèi)存對齊:編譯器為程序中的每個(gè)“數(shù)據(jù)單元”安排在適當(dāng)?shù)奈恢蒙?/p>
為什么要對齊?
一種提高內(nèi)存訪問速度的策略,cpu在訪問未對齊的內(nèi)存需要經(jīng)過兩次內(nèi)存訪問肃廓,而經(jīng)過內(nèi)存對齊一次就可以了.
舉例說明: 32bit的系統(tǒng)一次只能讀入32bit的數(shù)據(jù), 即4個(gè)字節(jié), 如果首地址為0, name讀入順序應(yīng)該是0-3, 3-7, 8-11 ...... 這樣的讀入; 在沒有對齊的情況下, 一個(gè)int變量(4字節(jié))可能分配中2,3,4,5這幾個(gè)位置上, 如果要獲取該變量, 就要進(jìn)行0-3, 4-7兩次讀取
像下面的struct:
typedef strutc test{
char a;
int b;
char c;
}
- 未對齊
讀取b需要0-3, 4-7兩次讀取
- 對齊
讀取b就可以從4-7一次讀取
對齊規(guī)則
1. 內(nèi)存的自然對齊
每一種數(shù)據(jù)類型都必須放在地址中的整數(shù)倍上
- char(1)類型可以放在任何位置
- short(2)只能放在0x2, 1x2, 2x2, 3x2.., 即0, 2, 4, 6 ...的位置上
- int(4)只能放在0x4, 1x4, 2x4, 3x4.., 即0, 4, 8, 12 ...的位置上
- double(8)只能放在0x8, 1x8, 2x8, 3x8.., 即0, 8, 16, 24 ...的位置上
note:以上規(guī)則針對的是#pragma pack(16)
和#pragma pack(8)
時(shí)適用, 對其系數(shù)為1,2,4, 參見下邊的說明
代碼:
#import "ViewController.h"
typedef struct test{
char a;
int b;
double c;
char d;
}STU;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
test();
}
int test(void)
{
STU s;
printf("s的大小是 = %d\n",(int)sizeof(STU));
printf("s中a的起始地址是 %p\n",&(s.a));
printf("s中b的起始地址是 %p\n",&(s.b));
printf("s中c的起始地址是 %p\n",&(s.c));
printf("s中d的起始地址是 %p\n",&(s.d));
return 0;
}
@end
輸出:
s的大小是 = 24
s中a的起始地址是 0x7ffee06479c8
s中b的起始地址是 0x7ffee06479cc
s中c的起始地址是 0x7ffee06479d0
s中d的起始地址是 0x7ffee06479d8
如果按照規(guī)則一, 推斷出來的應(yīng)該是這樣的:
四字節(jié)對齊, 長度應(yīng)該是20位
結(jié)果其實(shí)是這樣的:
長度24位, 最后一個(gè)char補(bǔ)了7位, 原因就在于規(guī)則二
另外修改對齊系數(shù), 的出來的結(jié)果也不一樣:
pragma pack(16)
s的大小是 = 24
s中a的起始地址是 0x7ffee3e7f9c8
s中b的起始地址是 0x7ffee3e7f9cc
s中c的起始地址是 0x7ffee3e7f9d0
s中d的起始地址是 0x7ffee3e7f9d8
pragma pack(8)
大小是 = 24
s中a的起始地址是 0x7ffee2ab49c8
s中b的起始地址是 0x7ffee2ab49cc
s中c的起始地址是 0x7ffee2ab49d0
s中d的起始地址是 0x7ffee2ab49d8
pragma pack(4)
s的大小是 = 20
s中a的起始地址是 0x7ffee81239c8
s中b的起始地址是 0x7ffee81239cc
s中c的起始地址是 0x7ffee81239d0
s中d的起始地址是 0x7ffee81239d8
pragma pack(2)
s的大小是 = 16
s中a的起始地址是 0x7ffee03f09d0
s中b的起始地址是 0x7ffee03f09d2
s中c的起始地址是 0x7ffee03f09d6
s中d的起始地址是 0x7ffee03f09de
pragma pack(1)
s的大小是 = 14
s中a的起始地址是 0x7ffee2eb49d0
s中b的起始地址是 0x7ffee2eb49d1
s中c的起始地址是 0x7ffee2eb49d5
s中d的起始地址是 0x7ffee2eb49dd
2.補(bǔ)齊規(guī)則
在經(jīng)過第一原則分析后纽竣,檢查計(jì)算出的存儲單元是否為所有元素中最寬的元素的長度的整數(shù)倍廉涕,是戴已,則結(jié)束切平;
若不是,則補(bǔ)齊為它的整數(shù)倍
因?yàn)榇a中:
typedef struct test{
char a;
int b;
double c;
char d;
}STU;
struct
的成員類型double
是8位長度, 所以struct
的大小必須是8的倍數(shù), 所以最后一位char
要補(bǔ)上7位
3. 包含結(jié)構(gòu)體成員的補(bǔ)齊規(guī)則
如果結(jié)構(gòu)體作為成員点楼,則要找到這個(gè)結(jié)構(gòu)體中的最大元素扫尖,然后從這個(gè)最大成員的整數(shù)倍地址開始存儲
代碼:
typedef struct
{
char a;
int b;
double c;
}X;
typedef struct {
char a;
X b;
}Y;
sizeof(X)為16,sizeof(Y)為24, 計(jì)算Y的存儲長度時(shí)掠廓,X的最大元素是double
, 所以在存放第二個(gè)元素b時(shí)的初始位置是在double
型的長度8的整數(shù)倍處换怖,而非16的整數(shù)倍處,即系統(tǒng)為b所分配的存儲空間是第8~23個(gè)字節(jié)
另外:包含指針類型的情況蟀瞧。只要記住指針本身所占的存儲空間是8個(gè)字節(jié)(64bit系統(tǒng), 32bit下是4字節(jié))就行了沉颂,而不必看它是指向什么類型的指針
typedef struct
{
int* b;
}X;
typedef struct {
char *a;
}Y;
輸出:
X的大小是 = 8
Y的大小是 = 8
對齊規(guī)則(百科中的描述)
每個(gè)特定平臺上的編譯器都有自己的默認(rèn)“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預(yù)編譯命令#pragma pack(n)悦污,n=1,2,4,8,16來改變這一系數(shù)铸屉,其中的n就是你要指定的“對齊系數(shù)”。
1塞关、數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員抬探,第一個(gè)數(shù)據(jù)成員放在offset為0的地方子巾,以后每個(gè)數(shù)據(jù)成員的對齊按照#pragma pack指定的數(shù)值和這個(gè)數(shù)據(jù)成員自身長度中帆赢,比較小的那個(gè)進(jìn)行。
2线梗、結(jié)構(gòu)(或聯(lián)合)的整體對齊規(guī)則:在數(shù)據(jù)成員完成各自對齊之后椰于,結(jié)構(gòu)(或聯(lián)合)本身也要進(jìn)行對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中仪搔,比較小的那個(gè)進(jìn)行瘾婿。
3、結(jié)合1、2可推斷:當(dāng)#pragma pack的n值等于或超過所有數(shù)據(jù)成員長度的時(shí)候偏陪,這個(gè)n值的大小將不產(chǎn)生任何效果抢呆。