總結一下記憶體對齊學習
什麼是記憶體對齊
以一個例子來說明,以64位系統為例
type test struct {
a int32
b byte
}
func main() {
fmt.Println(unsafe.Sizeof(test{})) // 8
}
理論上int32佔4個位元組,byte佔一個位元組,test結構體應該佔5個位元組才對。但實際上佔了8個位元組,這就是進行了位元組對齊。
為什麼會出現位元組對齊
現代計算機中記憶體空間都是按照 byte 劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何地址開始,但是實際的RAM記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,這和RAM的儲存原理有關。感興趣可以去看這個視訊。
儘管記憶體是以位元組為單位,但是大部分處理器並不是按位元組塊來存取記憶體的。它一般會以2位元組,4位元組(32位),8位元組(64位),16位元組甚至32位元組為單位來存取記憶體,我們將上述這些存取單位稱為記憶體存取粒度.
記憶體存取粒度又稱 對齊粒度(也叫對齊模數) 或稱 暫存器寬度 或稱 機器字長 或稱 匯流排寬度 。gcc中預設#pragma pack(4),可以通過預編譯命令#pragma pack(n),n = 1,2,4,8,16來改變這一系數。
假如沒有記憶體對齊機制,資料可以任意存放,現在一個int變數存放在從地址1開始的連續的4個位元組地址中,該處理器去取資料時,要先從0地址開始讀取第一個4位元組塊,剔除不想要的位元組(0地址),然後從地址4開始讀取下一個4位元組塊,同樣剔除不要的資料(5,6,7地址),最後留下的兩塊資料合併放入暫存器。這會多做很多工作。所以就出現了對齊機制。
建議看完上面的視訊再來看下面的例子。
對齊規則
go語言中對齊規則和c語言的一樣。 下面所有的描述都是對64位來說。
-
對於簡單型別,如果該型別大小超過8個位元組,用8個位元組對齊;小於8個位元組按該型別的大小對齊,即按小的對齊
-
對於結構體型別,它的預設對齊方式就是它的所有成員使用的對齊引數中最大的一個,這樣在成員是複雜型別時,可以最小化長度。
-
對齊後的長度必須是成員中最大的對齊引數的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊。
-
對於陣列,比如char a[1024];它的對齊方式和分別寫1024個char 是一樣的。雖然他的佔用大小超過了8,但他的對齊值仍然是1。
對於32位機器來說,機器字長是4;對於128位或更高的機器來說,可能是16,32,64...。
幾個例子
- 順序不同,最後的佔用大小不同
type User struct {
a uint8
b uint64
c uint16
d uint32
}
type User2 struct {
a uint8
c uint16
d uint32
b uint64
}
func main() {
fmt.Println(unsafe.Sizeof(User{})) // 24
fmt.Println(unsafe.Sizeof(User2{})) // 16
}
- 結構體中包含結構體 的時候的對齊案例
type User struct {
a uint8 //按1對齊 0%1=0 從0開始到1 佔1個位元組
b uint32 //按4對齊 4%4=0 從4開始到8 佔4個位元組
c uint16 //按2對齊 8%2=0 從8開始到10 佔2個位元組
//User 按最大的元素大小對齊,即uint32=4
//User到10,10不是4的倍數,往後擴充,所以User佔12,對齊值是4
}
type User2 struct {
a uint8 // 按1對齊 0%1=0 0-1 佔1 注意是:[0, 1)下同
c uint16 // 按2對齊 2%2=0 2-4 佔2 [2, 4)
//User:佔12個位元組,對齊值為4
u User // 按4對齊 4%4=0 4-16 佔12
d uint32 // 按4對齊 16%4==0 16-20 佔4
b uint64 // 按8對齊 24%4==0 24-32 佔8
//User2 按最大的元素大小對齊,即uint64=8
//User2到32 32是8的倍數,所以User2佔32,對齊值是8
}
func main() {
fmt.Println(unsafe.Sizeof(User{})) // 12
fmt.Println(unsafe.Sizeof(User2{})) // 32
}
- 結構體中包含陣列
type two struct {
s [100]byte // 按byte=1對齊,
//two按最大的元素大小 byte=1 對齊,
//two到100 100是1的倍數,所以two佔100,對齊值是1
}
type User2 struct {
a uint8 // 按1對齊 0%1=0 0-1 佔1
c uint16 // 按2對齊 2%2=0 2-4 佔2
//two:佔100個位元組,但是按元素中最大的對齊(按1對齊),
s two // 按1對齊 4%1=0 4-104 佔100
d uint32 // 按4對齊 104%4=0 104-108 佔4
b uint64 // 按8對齊 108不行,往後找 112%4=0 112-120 佔8
//User2按最大的元素大小 8 對齊,
//User2到120 120正好是8的倍數,所以User2佔120
}
func main() {
fmt.Println(unsafe.Sizeof(two{})) // 100
fmt.Println(unsafe.Sizeof(User2{})) // 120
}
- 結構體包含的結構體中包含陣列和其他元素
type two struct {
s [101]byte // 按byte=1對齊,0-101
b uint64 // 按8對齊 104%8=0 104-112 佔8
//two按最大的元素大小 8 對齊,
//two到112 112是8的倍數,所以two佔112,對齊值為8
}
type User2 struct {
a uint8 // 按1對齊 0%1=0 0-1 佔1
c uint16 // 按2對齊 2%2=0 2-4 佔2
//two:佔112個位元組,但是按元素中最大的對齊(按8對齊),
s two // 按8對齊 8%8=0 8-120 佔112
d uint32 // 按4對齊 120%4=0 120-124 佔4
b uint64 // 按8對齊 128%8=0 128-136 佔8
//User2按最大的元素大小 8 對齊,
//User2到136 136正好是8的倍數,所以User2佔136
}
func main() {
fmt.Println(unsafe.Sizeof(two{})) // 112
fmt.Println(unsafe.Sizeof(User2{}))// 136
}
推薦閱讀