1. 程式人生 > 實用技巧 >總結一下記憶體對齊學習

總結一下記憶體對齊學習

什麼是記憶體對齊

以一個例子來說明,以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...。

幾個例子

  1. 順序不同,最後的佔用大小不同
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
}
  1. 結構體中包含結構體 的時候的對齊案例
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
}
  1. 結構體中包含陣列
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
}
  1. 結構體包含的結構體中包含陣列和其他元素
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
}

推薦閱讀