Go 語(yǔ)言 常量和全局變量

2023-03-22 15:01 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-03-const-and-var.html


3.3 常量和全局變量

程序中的一切變量的初始值都直接或間接地依賴常量或常量表達(dá)式生成。在 Go 語(yǔ)言中很多變量是默認(rèn)零值初始化的,但是 Go 匯編中定義的變量最好還是手工通過(guò)常量初始化。有了常量之后,就可以衍生定義全局變量,并使用常量組成的表達(dá)式初始化其它各種變量。本節(jié)將簡(jiǎn)單討論 Go 匯編語(yǔ)言中常量和全局變量的用法。

3.3.1 常量

Go 匯編語(yǔ)言中常量以 $ 美元符號(hào)為前綴。常量的類(lèi)型有整數(shù)常量、浮點(diǎn)數(shù)常量、字符常量和字符串常量等幾種類(lèi)型。以下是幾種類(lèi)型常量的例子:

$1           // 十進(jìn)制
$0xf4f8fcff  // 十六進(jìn)制
$1.5         // 浮點(diǎn)數(shù)
$'a'         // 字符
$"abcd"      // 字符串

其中整數(shù)類(lèi)型常量默認(rèn)是十進(jìn)制格式,也可以用十六進(jìn)制格式表示整數(shù)常量。所有的常量最終都必須和要初始化的變量?jī)?nèi)存大小匹配。

對(duì)于數(shù)值型常量,可以通過(guò)常量表達(dá)式構(gòu)成新的常量:

$2+2      // 常量表達(dá)式
$3&1<<2   // == $4
$(3&1)<<2 // == $4

其中常量表達(dá)式中運(yùn)算符的優(yōu)先級(jí)和 Go 語(yǔ)言保持一致。

Go 匯編語(yǔ)言中的常量其實(shí)不僅僅只有編譯時(shí)常量,還包含運(yùn)行時(shí)常量。比如包中全局的變量和全局函數(shù)在運(yùn)行時(shí)地址也是固定不變的,這里地址不會(huì)改變的包變量和函數(shù)的地址也是一種匯編常量。

下面是本章第一節(jié)用匯編定義的字符串代碼:

GLOBL ·NameData(SB),$8
DATA  ·NameData(SB)/8,$"gopher"

GLOBL ·Name(SB),$16
DATA  ·Name+0(SB)/8,$·NameData(SB)
DATA  ·Name+8(SB)/8,$6

其中 $·NameData(SB) 也是以 $ 美元符號(hào)為前綴,因此也可以將它看作是一個(gè)常量,它對(duì)應(yīng)的是 NameData 包變量的地址。在匯編指令中,我們也可以通過(guò) LEA 指令來(lái)獲取 NameData 變量的地址。

3.3.2 全局變量

在 Go 語(yǔ)言中,變量根據(jù)作用域和生命周期有全局變量和局部變量之分。全局變量是包一級(jí)的變量,全局變量一般有著較為固定的內(nèi)存地址,生命周期跨越整個(gè)程序運(yùn)行時(shí)間。而局部變量一般是函數(shù)內(nèi)定義的的變量,只有在函數(shù)被執(zhí)行的時(shí)間才被在棧上創(chuàng)建,當(dāng)函數(shù)調(diào)用完成后將回收(暫時(shí)不考慮閉包對(duì)局部變量捕獲的問(wèn)題)。

從 Go 匯編語(yǔ)言角度來(lái)看,全局變量和局部變量有著非常大的差異。在 Go 匯編中全局變量和全局函數(shù)更為相似,都是通過(guò)一個(gè)人為定義的符號(hào)來(lái)引用對(duì)應(yīng)的內(nèi)存,區(qū)別只是內(nèi)存中存放是數(shù)據(jù)還是要執(zhí)行的指令。因?yàn)樵隈T諾伊曼系統(tǒng)結(jié)構(gòu)的計(jì)算機(jī)中指令也是數(shù)據(jù),而且指令和數(shù)據(jù)存放在統(tǒng)一編址的內(nèi)存中。因?yàn)橹噶詈蛿?shù)據(jù)并沒(méi)有本質(zhì)的差別,因此我們甚至可以像操作數(shù)據(jù)那樣動(dòng)態(tài)生成指令(這是所有 JIT 技術(shù)的原理)。而局部變量則需在了解了匯編函數(shù)之后,才能通過(guò) SP ??臻g來(lái)隱式定義。

在 Go 匯編語(yǔ)言中,內(nèi)存是通過(guò) SB 偽寄存器定位。SB 是 Static base pointer 的縮寫(xiě),意為靜態(tài)內(nèi)存的開(kāi)始地址。我們可以將 SB 想象為一個(gè)和內(nèi)容容量有相同大小的字節(jié)數(shù)組,所有的靜態(tài)全局符號(hào)通??梢酝ㄟ^(guò) SB 加一個(gè)偏移量定位,而我們定義的符號(hào)其實(shí)就是相對(duì)于 SB 內(nèi)存開(kāi)始地址偏移量。對(duì)于 SB 偽寄存器,全局變量和全局函數(shù)的符號(hào)并沒(méi)有任何區(qū)別。

要定義全局變量,首先要聲明一個(gè)變量對(duì)應(yīng)的符號(hào),以及變量對(duì)應(yīng)的內(nèi)存大小。導(dǎo)出變量符號(hào)的語(yǔ)法如下:

GLOBL symbol(SB), width

GLOBL 匯編指令用于定義名為 symbol 的變量,變量對(duì)應(yīng)的內(nèi)存寬度為 width,內(nèi)存寬度部分必須用常量初始化。下面的代碼通過(guò)匯編定義一個(gè) int32 類(lèi)型的 count 變量:

GLOBL ·count(SB),$4

其中符號(hào) ·count 以中點(diǎn)開(kāi)頭表示是當(dāng)前包的變量,最終符號(hào)名為被展開(kāi)為 path/to/pkg.count。count 變量的大小是 4 個(gè)字節(jié),常量必須以 $ 美元符號(hào)開(kāi)頭。內(nèi)存的寬度必須是 2 的指數(shù)倍,編譯器最終會(huì)保證變量的真實(shí)地址對(duì)齊到機(jī)器字倍數(shù)。需要注意的是,在 Go 匯編中我們無(wú)法為 count 變量指定具體的類(lèi)型。在匯編中定義全局變量時(shí),我們只關(guān)心變量的名字和內(nèi)存大小,變量最終的類(lèi)型只能在 Go 語(yǔ)言中聲明。

變量定義之后,我們可以通過(guò) DATA 匯編指令指定對(duì)應(yīng)內(nèi)存中的數(shù)據(jù),語(yǔ)法如下:

DATA symbol+offset(SB)/width, value

具體的含義是從 symbol+offset 偏移量開(kāi)始,width 寬度的內(nèi)存,用 value 常量對(duì)應(yīng)的值初始化。DATA 初始化內(nèi)存時(shí),width 必須是 1、2、4、8 幾個(gè)寬度之一,因?yàn)樵俅蟮膬?nèi)存無(wú)法一次性用一個(gè) uint64 大小的值表示。

對(duì)于 int32 類(lèi)型的 count 變量來(lái)說(shuō),我們既可以逐個(gè)字節(jié)初始化,也可以一次性初始化:

DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4

// or

DATA ·count+0(SB)/4,$0x04030201

因?yàn)?X86 處理器是小端序,因此用十六進(jìn)制 0x04030201 初始化全部的 4 個(gè)字節(jié),和用 1、2、3、4 逐個(gè)初始化 4 個(gè)字節(jié)是一樣的效果。

最后還需要在 Go 語(yǔ)言中聲明對(duì)應(yīng)的變量(和 C 語(yǔ)言頭文件聲明變量的作用類(lèi)似),這樣垃圾回收器會(huì)根據(jù)變量的類(lèi)型來(lái)管理其中的指針相關(guān)的內(nèi)存數(shù)據(jù)。

3.3.2.1 數(shù)組類(lèi)型

匯編中數(shù)組也是一種非常簡(jiǎn)單的類(lèi)型。Go 語(yǔ)言中數(shù)組是一種有著扁平內(nèi)存結(jié)構(gòu)的基礎(chǔ)類(lèi)型。因此 [2]byte 類(lèi)型和 [1]uint16 類(lèi)型有著相同的內(nèi)存結(jié)構(gòu)。只有當(dāng)數(shù)組和結(jié)構(gòu)體結(jié)合之后情況才會(huì)變的稍微復(fù)雜。

下面我們嘗試用匯編定義一個(gè) [2]int 類(lèi)型的數(shù)組變量 num:

var num [2]int

然后在匯編中定義一個(gè)對(duì)應(yīng) 16 字節(jié)大小的變量,并用零值進(jìn)行初始化:

GLOBL ·num(SB),$16
DATA ·num+0(SB)/8,$0
DATA ·num+8(SB)/8,$0

下圖是 Go 語(yǔ)句和匯編語(yǔ)句定義變量時(shí)的對(duì)應(yīng)關(guān)系:


圖 3-4 變量定義

匯編代碼中并不需要 NOPTR 標(biāo)志,因?yàn)?Go 編譯器會(huì)從 Go 語(yǔ)言語(yǔ)句聲明的 [2]int 類(lèi)型中推導(dǎo)出該變量?jī)?nèi)部沒(méi)有指針數(shù)據(jù)。

3.3.2.2 bool 型變量

Go 匯編語(yǔ)言定義變量無(wú)法指定類(lèi)型信息,因此需要先通過(guò) Go 語(yǔ)言聲明變量的類(lèi)型。以下是在 Go 語(yǔ)言中聲明的幾個(gè) bool 類(lèi)型變量:

var (
    boolValue  bool
    trueValue  bool
    falseValue bool
)

在 Go 語(yǔ)言中聲明的變量不能含有初始化語(yǔ)句。然后下面是 amd64 環(huán)境的匯編定義:

GLOBL ·boolValue(SB),$1   // 未初始化

GLOBL ·trueValue(SB),$1   // var trueValue = true
DATA ·trueValue(SB)/1,$1  // 非 0 均為 true

GLOBL ·falseValue(SB),$1  // var falseValue = false
DATA ·falseValue(SB)/1,$0

bool 類(lèi)型的內(nèi)存大小為 1 個(gè)字節(jié)。并且匯編中定義的變量需要手工指定初始化值,否則將可能導(dǎo)致產(chǎn)生未初始化的變量。當(dāng)需要將 1 個(gè)字節(jié)的 bool 類(lèi)型變量加載到 8 字節(jié)的寄存器時(shí),需要使用 MOVBQZX 指令將不足的高位用 0 填充。

3.3.2.3 int 型變量

所有的整數(shù)類(lèi)型均有類(lèi)似的定義的方式,比較大的差異是整數(shù)類(lèi)型的內(nèi)存大小和整數(shù)是否是有符號(hào)。下面是聲明的 int32 和 uint32 類(lèi)型變量:

var int32Value int32

var uint32Value uint32

在 Go 語(yǔ)言中聲明的變量不能含有初始化語(yǔ)句。然后下面是 amd64 環(huán)境的匯編定義:

GLOBL ·int32Value(SB),$4
DATA ·int32Value+0(SB)/1,$0x01  // 第 0 字節(jié)
DATA ·int32Value+1(SB)/1,$0x02  // 第 1 字節(jié)
DATA ·int32Value+2(SB)/2,$0x03  // 第 3-4 字節(jié)

GLOBL ·uint32Value(SB),$4
DATA ·uint32Value(SB)/4,$0x01020304 // 第 1-4 字節(jié)

匯編定義變量時(shí)初始化數(shù)據(jù)并不區(qū)分整數(shù)是否有符號(hào)。只有在 CPU 指令處理該寄存器數(shù)據(jù)時(shí),才會(huì)根據(jù)指令的類(lèi)型來(lái)取分?jǐn)?shù)據(jù)的類(lèi)型或者是否帶有符號(hào)位。

3.3.2.4 float 型變量

Go 匯編語(yǔ)言通常無(wú)法區(qū)分變量是否是浮點(diǎn)數(shù)類(lèi)型,與之相關(guān)的浮點(diǎn)數(shù)機(jī)器指令會(huì)將變量當(dāng)作浮點(diǎn)數(shù)處理。Go 語(yǔ)言的浮點(diǎn)數(shù)遵循 IEEE754 標(biāo)準(zhǔn),有 float32 單精度浮點(diǎn)數(shù)和 float64 雙精度浮點(diǎn)數(shù)之分。

IEEE754 標(biāo)準(zhǔn)中,最高位 1bit 為符號(hào)位,然后是指數(shù)位(指數(shù)為采用移碼格式表示),然后是有效數(shù)部分(其中小數(shù)點(diǎn)左邊的一個(gè) bit 位被省略)。下圖是 IEEE754 中 float32 類(lèi)型浮點(diǎn)數(shù)的 bit 布局:


圖 3-5 IEEE754 浮點(diǎn)數(shù)結(jié)構(gòu)

IEEE754 浮點(diǎn)數(shù)還有一些奇妙的特性:比如有正負(fù)兩個(gè) 0;除了無(wú)窮大和無(wú)窮小 Inf 還有非數(shù) NaN;同時(shí)如果兩個(gè)浮點(diǎn)數(shù)有序那么對(duì)應(yīng)的有符號(hào)整數(shù)也是有序的(反之則不一定成立,因?yàn)楦↑c(diǎn)數(shù)中存在的非數(shù)是不可排序的)。浮點(diǎn)數(shù)是程序中最難琢磨的角落,因?yàn)槌绦蛑泻芏嗍謱?xiě)的浮點(diǎn)數(shù)字面值常量根本無(wú)法精確表達(dá),浮點(diǎn)數(shù)計(jì)算涉及到的誤差舍入方式可能也的隨機(jī)的。

下面是在 Go 語(yǔ)言中聲明兩個(gè)浮點(diǎn)數(shù)(如果沒(méi)有在匯編中定義變量,那么聲明的同時(shí)也會(huì)定義變量)。

var float32Value float32

var float64Value float64

然后在匯編中定義并初始化上面聲明的兩個(gè)浮點(diǎn)數(shù):

GLOBL ·float32Value(SB),$4
DATA ·float32Value+0(SB)/4,$1.5      // var float32Value = 1.5

GLOBL ·float64Value(SB),$8
DATA ·float64Value(SB)/8,$0x01020304 // bit 方式初始化

我們?cè)谏弦还?jié)精簡(jiǎn)的算術(shù)指令中都是針對(duì)整數(shù),如果要通過(guò)整數(shù)指令處理浮點(diǎn)數(shù)的加減法必須根據(jù)浮點(diǎn)數(shù)的運(yùn)算規(guī)則進(jìn)行:先對(duì)齊小數(shù)點(diǎn),然后進(jìn)行整數(shù)加減法,最后再對(duì)結(jié)果進(jìn)行歸一化并處理精度舍入問(wèn)題。不過(guò)在目前的主流 CPU 中,都針對(duì)浮點(diǎn)數(shù)提供了專(zhuān)有的計(jì)算指令。

3.3.2.5 string 類(lèi)型變量

從 Go 匯編語(yǔ)言角度看,字符串只是一種結(jié)構(gòu)體。string 的頭結(jié)構(gòu)定義如下:

type reflect.StringHeader struct {
    Data uintptr
    Len  int
}

在 amd64 環(huán)境中 StringHeader 有 16 個(gè)字節(jié)大小,因此我們先在 Go 代碼聲明字符串變量,然后在匯編中定義一個(gè) 16 字節(jié)大小的變量:

var helloworld string
GLOBL ·helloworld(SB),$16

同時(shí)我們可以為字符串準(zhǔn)備真正的數(shù)據(jù)。在下面的匯編代碼中,我們定義了一個(gè) text 當(dāng)前文件內(nèi)的私有變量(以 <> 為后綴名),內(nèi)容為 “Hello World!”:

GLOBL text<>(SB),NOPTR,$16
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!"

雖然 text<> 私有變量表示的字符串只有 12 個(gè)字符長(zhǎng)度,但是我們依然需要將變量的長(zhǎng)度擴(kuò)展為 2 的指數(shù)倍數(shù),這里也就是 16 個(gè)字節(jié)的長(zhǎng)度。其中 NOPTR 表示 text<> 不包含指針數(shù)據(jù)。

然后使用 text 私有變量對(duì)應(yīng)的內(nèi)存地址對(duì)應(yīng)的常量來(lái)初始化字符串頭結(jié)構(gòu)體中的 Data 部分,并且手工指定 Len 部分為字符串的長(zhǎng)度:

DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12         // StringHeader.Len

需要注意的是,字符串是只讀類(lèi)型,要避免在匯編中直接修改字符串底層數(shù)據(jù)的內(nèi)容。

3.3.2.6 slice 類(lèi)型變量

slice 變量和 string 變量相似,只不過(guò)是對(duì)應(yīng)的是切片頭結(jié)構(gòu)體而已。切片頭的結(jié)構(gòu)如下:

type reflect.SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

對(duì)比可以發(fā)現(xiàn),切片的頭的前 2 個(gè)成員字符串是一樣的。因此我們可以在前面字符串變量的基礎(chǔ)上,再擴(kuò)展一個(gè) Cap 成員就成了切片類(lèi)型了:

var helloworld []byte
GLOBL ·helloworld(SB),$24            // var helloworld []byte("Hello World!")
DATA ·helloworld+0(SB)/8,$text<>(SB) // SliceHeader.Data
DATA ·helloworld+8(SB)/8,$12         // SliceHeader.Len
DATA ·helloworld+16(SB)/8,$16        // SliceHeader.Cap

GLOBL text<>(SB),$16
DATA text<>+0(SB)/8,$"Hello Wo"      // ...string data...
DATA text<>+8(SB)/8,$"rld!"          // ...string data...

因?yàn)榍衅妥址南嗳菪?,我們可以將切片頭的前 16 個(gè)字節(jié)臨時(shí)作為字符串使用,這樣可以省去不必要的轉(zhuǎn)換。

3.3.2.7 map/channel 類(lèi)型變量

map/channel 等類(lèi)型并沒(méi)有公開(kāi)的內(nèi)部結(jié)構(gòu),它們只是一種未知類(lèi)型的指針,無(wú)法直接初始化。在匯編代碼中我們只能為類(lèi)似變量定義并進(jìn)行 0 值初始化:

var m map[string]int

var ch chan int
GLOBL ·m(SB),$8  // var m map[string]int
DATA  ·m+0(SB)/8,$0

GLOBL ·ch(SB),$8 // var ch chan int
DATA  ·ch+0(SB)/8,$0

其實(shí)在 runtime 包中為匯編提供了一些輔助函數(shù)。比如在匯編中可以通過(guò) runtime.makemap 和 runtime.makechan 內(nèi)部函數(shù)來(lái)創(chuàng)建 map 和 chan 變量。輔助函數(shù)的簽名如下:

func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
func makechan(chanType *byte, size int) (hchan chan any)

需要注意的是,makemap 是一種泛型函數(shù),可以創(chuàng)建不同類(lèi)型的 map,map 的具體類(lèi)型是通過(guò) mapType 參數(shù)指定。

3.3.3 變量的內(nèi)存布局

我們已經(jīng)多次強(qiáng)調(diào),在 Go 匯編語(yǔ)言中變量是沒(méi)有類(lèi)型的。因此在 Go 語(yǔ)言中有著不同類(lèi)型的變量,底層可能對(duì)應(yīng)的是相同的內(nèi)存結(jié)構(gòu)。深刻理解每個(gè)變量的內(nèi)存布局是匯編編程時(shí)的必備條件。

首先查看前面已經(jīng)見(jiàn)過(guò)的 [2]int 類(lèi)型數(shù)組的內(nèi)存布局:


圖 3-6 變量定義

變量在 data 段分配空間,數(shù)組的元素地址依次從低向高排列。

然后再查看下標(biāo)準(zhǔn)庫(kù)圖像包中 image.Point 結(jié)構(gòu)體類(lèi)型變量的內(nèi)存布局:


圖 3-7 結(jié)構(gòu)體變量定義

變量也是在 data 段分配空間,變量結(jié)構(gòu)體成員的地址也是依次從低向高排列。

因此 [2]int 和 image.Point 類(lèi)型底層有著近似相同的內(nèi)存布局。

3.3.4 標(biāo)識(shí)符規(guī)則和特殊標(biāo)志

Go 語(yǔ)言的標(biāo)識(shí)符可以由絕對(duì)的包路徑加標(biāo)識(shí)符本身定位,因此不同包中的標(biāo)識(shí)符即使同名也不會(huì)有問(wèn)題。Go 匯編是通過(guò)特殊的符號(hào)來(lái)表示斜杠和點(diǎn)符號(hào),因?yàn)檫@樣可以簡(jiǎn)化匯編器詞法掃描部分代碼的編寫(xiě),只要通過(guò)字符串替換就可以了。

下面是匯編中常見(jiàn)的幾種標(biāo)識(shí)符的使用方式(通常也適用于函數(shù)標(biāo)識(shí)符):

GLOBL ·pkg_name1(SB),$1
GLOBL main·pkg_name2(SB),$1
GLOBL my/pkg·pkg_name(SB),$1

此外,Go 匯編中可以定義僅當(dāng)前文件可以訪問(wèn)的私有標(biāo)識(shí)符(類(lèi)似 C 語(yǔ)言中文件內(nèi) static 修飾的變量),以 <> 為后綴名:

GLOBL file_private<>(SB),$1

這樣可以減少私有標(biāo)識(shí)符對(duì)其它文件內(nèi)標(biāo)識(shí)符命名的干擾。

此外,Go 匯編語(yǔ)言還在 "textflag.h" 文件定義了一些標(biāo)志。其中用于變量的標(biāo)志有 DUPOK、RODATA 和 NOPTR 幾個(gè)。DUPOK 表示該變量對(duì)應(yīng)的標(biāo)識(shí)符可能有多個(gè),在鏈接時(shí)只選擇其中一個(gè)即可(一般用于合并相同的常量字符串,減少重復(fù)數(shù)據(jù)占用的空間)。RODATA 標(biāo)志表示將變量定義在只讀內(nèi)存段,因此后續(xù)任何對(duì)此變量的修改操作將導(dǎo)致異常(recover 也無(wú)法捕獲)。NOPTR 則表示此變量的內(nèi)部不含指針數(shù)據(jù),讓垃圾回收器忽略對(duì)該變量的掃描。如果變量已經(jīng)在 Go 代碼中聲明過(guò)的話,Go 編譯器會(huì)自動(dòng)分析出該變量是否包含指針,這種時(shí)候可以不用手寫(xiě) NOPTR 標(biāo)志。

比如下面的例子是通過(guò)匯編來(lái)定義一個(gè)只讀的 int 類(lèi)型的變量:

var const_id int // readonly
#include "textflag.h"

GLOBL ·const_id(SB),NOPTR|RODATA,$8
DATA  ·const_id+0(SB)/8,$9527

我們使用 #include 語(yǔ)句包含定義標(biāo)志的 "textflag.h" 頭文件(和 C 語(yǔ)言中預(yù)處理相同)。然后 GLOBL 匯編命令在定義變量時(shí),給變量增加了 NOPTR 和 RODATA 兩個(gè)標(biāo)志(多個(gè)標(biāo)志之間采用豎杠分割),表示變量中沒(méi)有指針數(shù)據(jù)同時(shí)定義在只讀數(shù)據(jù)段。

變量一般也叫可取地址的值,但是 const_id 雖然可以取地址,但是確實(shí)不能修改。不能修改的限制并不是由編譯器提供,而是因?yàn)閷?duì)該變量的修改會(huì)導(dǎo)致對(duì)只讀內(nèi)存段進(jìn)行寫(xiě),從而導(dǎo)致異常。

3.3.5 小結(jié)

以上我們初步展示了通過(guò)匯編定義全局變量的用法。但是真實(shí)的環(huán)境中我們并不推薦通過(guò)匯編定義變量——因?yàn)橛?Go 語(yǔ)言定義變量更加簡(jiǎn)單和安全。在 Go 語(yǔ)言中定義變量,編譯器可以幫助我們計(jì)算好變量的大小,生成變量的初始值,同時(shí)也包含了足夠的類(lèi)型信息。匯編語(yǔ)言的優(yōu)勢(shì)是挖掘機(jī)器的特性和性能,用匯編定義變量則無(wú)法發(fā)揮這些優(yōu)勢(shì)。因此在理解了匯編定義變量的用法后,建議大家謹(jǐn)慎使用。



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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)