Go 語(yǔ)言 unsafe.Sizeof, Alignof 和 Offsetof

2023-03-14 17:00 更新

原文鏈接:https://gopl-zh.github.io/ch13/ch13-01.html


13.1. unsafe.Sizeof, Alignof 和 Offsetof

unsafe.Sizeof函數(shù)返回操作數(shù)在內(nèi)存中的字節(jié)大小,參數(shù)可以是任意類(lèi)型的表達(dá)式,但是它并不會(huì)對(duì)表達(dá)式進(jìn)行求值。一個(gè)Sizeof函數(shù)調(diào)用是一個(gè)對(duì)應(yīng)uintptr類(lèi)型的常量表達(dá)式,因此返回的結(jié)果可以用作數(shù)組類(lèi)型的長(zhǎng)度大小,或者用作計(jì)算其他的常量。

import "unsafe"
fmt.Println(unsafe.Sizeof(float64(0))) // "8"

Sizeof函數(shù)返回的大小只包括數(shù)據(jù)結(jié)構(gòu)中固定的部分,例如字符串對(duì)應(yīng)結(jié)構(gòu)體中的指針和字符串長(zhǎng)度部分,但是并不包含指針指向的字符串的內(nèi)容。Go語(yǔ)言中非聚合類(lèi)型通常有一個(gè)固定的大小,盡管在不同工具鏈下生成的實(shí)際大小可能會(huì)有所不同??紤]到可移植性,引用類(lèi)型或包含引用類(lèi)型的大小在32位平臺(tái)上是4個(gè)字節(jié),在64位平臺(tái)上是8個(gè)字節(jié)。

計(jì)算機(jī)在加載和保存數(shù)據(jù)時(shí),如果內(nèi)存地址合理地對(duì)齊的將會(huì)更有效率。例如2字節(jié)大小的int16類(lèi)型的變量地址應(yīng)該是偶數(shù),一個(gè)4字節(jié)大小的rune類(lèi)型變量的地址應(yīng)該是4的倍數(shù),一個(gè)8字節(jié)大小的float64、uint64或64-bit指針類(lèi)型變量的地址應(yīng)該是8字節(jié)對(duì)齊的。但是對(duì)于再大的地址對(duì)齊倍數(shù)則是不需要的,即使是complex128等較大的數(shù)據(jù)類(lèi)型最多也只是8字節(jié)對(duì)齊。

由于地址對(duì)齊這個(gè)因素,一個(gè)聚合類(lèi)型(結(jié)構(gòu)體或數(shù)組)的大小至少是所有字段或元素大小的總和,或者更大因?yàn)榭赡艽嬖趦?nèi)存空洞。內(nèi)存空洞是編譯器自動(dòng)添加的沒(méi)有被使用的內(nèi)存空間,用于保證后面每個(gè)字段或元素的地址相對(duì)于結(jié)構(gòu)或數(shù)組的開(kāi)始地址能夠合理地對(duì)齊(譯注:內(nèi)存空洞可能會(huì)存在一些隨機(jī)數(shù)據(jù),可能會(huì)對(duì)用unsafe包直接操作內(nèi)存的處理產(chǎn)生影響)。

類(lèi)型 大小
bool 1個(gè)字節(jié)
intN, uintN, floatN, complexN N/8個(gè)字節(jié)(例如float64是8個(gè)字節(jié))
int, uint, uintptr 1個(gè)機(jī)器字
*T 1個(gè)機(jī)器字
string 2個(gè)機(jī)器字(data、len)
[]T 3個(gè)機(jī)器字(data、len、cap)
map 1個(gè)機(jī)器字
func 1個(gè)機(jī)器字
chan 1個(gè)機(jī)器字
interface 2個(gè)機(jī)器字(type、value)

Go語(yǔ)言的規(guī)范并沒(méi)有要求一個(gè)字段的聲明順序和內(nèi)存中的順序是一致的,所以理論上一個(gè)編譯器可以隨意地重新排列每個(gè)字段的內(nèi)存位置,雖然在寫(xiě)作本書(shū)的時(shí)候編譯器還沒(méi)有這么做。下面的三個(gè)結(jié)構(gòu)體雖然有著相同的字段,但是第一種寫(xiě)法比另外的兩個(gè)需要多50%的內(nèi)存。

                               // 64-bit  32-bit
struct{ bool; float64; int16 } // 3 words 4words
struct{ float64; int16; bool } // 2 words 3words
struct{ bool; int16; float64 } // 2 words 3words

關(guān)于內(nèi)存地址對(duì)齊算法的細(xì)節(jié)超出了本書(shū)的范圍,也不是每一個(gè)結(jié)構(gòu)體都需要擔(dān)心這個(gè)問(wèn)題,不過(guò)有效的包裝可以使數(shù)據(jù)結(jié)構(gòu)更加緊湊(譯注:未來(lái)的Go語(yǔ)言編譯器應(yīng)該會(huì)默認(rèn)優(yōu)化結(jié)構(gòu)體的順序,當(dāng)然應(yīng)該也能夠指定具體的內(nèi)存布局,相同討論請(qǐng)參考 Issue10014 ),內(nèi)存使用率和性能都可能會(huì)受益。

unsafe.Alignof 函數(shù)返回對(duì)應(yīng)參數(shù)的類(lèi)型需要對(duì)齊的倍數(shù)。和 Sizeof 類(lèi)似, Alignof 也是返回一個(gè)常量表達(dá)式,對(duì)應(yīng)一個(gè)常量。通常情況下布爾和數(shù)字類(lèi)型需要對(duì)齊到它們本身的大?。ㄗ疃?個(gè)字節(jié)),其它的類(lèi)型對(duì)齊到機(jī)器字大小。

unsafe.Offsetof 函數(shù)的參數(shù)必須是一個(gè)字段 x.f,然后返回 f 字段相對(duì)于 x 起始地址的偏移量,包括可能的空洞。

圖 13.1 顯示了一個(gè)結(jié)構(gòu)體變量 x 以及其在32位和64位機(jī)器上的典型的內(nèi)存?;疑珔^(qū)域是空洞。

var x struct {
    a bool
    b int16
    c []int
}

下面顯示了對(duì)x和它的三個(gè)字段調(diào)用unsafe包相關(guān)函數(shù)的計(jì)算結(jié)果:


32位系統(tǒng):

Sizeof(x)   = 16  Alignof(x)   = 4
Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 12  Alignof(x.c) = 4 Offsetof(x.c) = 4

64位系統(tǒng):

Sizeof(x)   = 32  Alignof(x)   = 8
Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 24  Alignof(x.c) = 8 Offsetof(x.c) = 8

雖然這幾個(gè)函數(shù)在不安全的unsafe包,但是這幾個(gè)函數(shù)調(diào)用并不是真的不安全,特別在需要優(yōu)化內(nèi)存空間時(shí)它們返回的結(jié)果對(duì)于理解原生的內(nèi)存布局很有幫助。



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)