成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文帶你了解【Go】初始化函數

開發 前端
本文完整地、詳細地介紹了Go中關于初始化函數相關的內容。相信在認真刨析了初始化函數的所有細節之后,對Go有了更近一步的了解。

[[425952]]

環境

  1. OS : Ubuntu 20.04.2 LTS; x86_64 
  2. Go : go version go1.16.2 linux/amd64 

包初始化

初始化函數與其他普通函數一樣,都隸屬于定義它的包(package),以下統稱為當前包。

一般來講,一個包初始化過程分三步:

  1. 初始化當前包依賴的所有包,包括依賴包的依賴包。
  2. 初始化當前包所有具有初始值的全局變量。
  3. 執行當前包的所有初始化函數。

關于這個過程,本文會一一詳細介紹。

基本定義

在Golang中有一類特殊的初始化函數,其定義格式如下:

  1. package pkg 
  2.  
  3. func init() { 
  4.   // to do sth 

初始化函數一個特殊之處是:其在可執行程序的main入口函數執行之前自動執行,而且不可被直接調用!

重復聲明

初始化函數第二個特殊之處是:在同一個包下,可以重復定義多次。

普通函數在同一個包下不可以重名,否則變異失敗:xxx redeclared in this block。

編譯重命名

初始化函數第三個特殊之處是:編譯重命名規則與普通函數不同。

普通函數在編譯過程中一般重命名規則為“[模塊名].包名.函數名”。

初始化函數在源碼中雖然名稱為init,但在編譯過程中重命名規則為“[模塊名].包名.init.數字后綴”。

例如:

  • 在上述的 func_init.0.go 源文件編譯之后,init函數被重命名為:main.init.0。
  • 在上述的 func_init.1.go 源文件編譯之后,兩個init函數分別被重命名為:main.init.0、main.init.1。

如上所示,如果同一個包下有多個init函數,重命名時后綴數字按順序增加一。

為什么會這樣呢?

那是因為Golang編譯器對 init 函數進行了特殊處理,相關源碼位于 cmd/compile/internal/gc/init.go 文件中。

全局變量 renameinitgen 用于記錄當前包名下init函數的數量以及下一個init函數后綴的值。

每當Golang編譯器遇到一個名稱為 init 的函數,就會調用一次 renameinit() 函數,最終 init 函數變得不可被調用。

為什么重命名init函數?

如上述我們看到的,在同一個包下可以重復聲明 init 函數,這可能是需要重命名的原因。

當我們繼續探究時,可能更加接近真相。

有一點需要明確并始終堅信:除全局常量和全局變量的聲明之外,所有的可執行代碼都必須在函數內執行。

通常情況下,代碼編譯之后,

  1. 聲明的全局常量可能被存儲在可執行文件的.rodata section。
  2. 聲明的全局變量可能被存儲在可執行文件的.data、.bss、.noptrdata等section。
  3. 聲明的函數或方法被編譯為機器指令存儲在可執行文件的.text section。

那么,以下代碼中(func_init.go),聲明全局變量的同時進行初始化賦值,該如何編譯呢? 

以下代碼屬于變量聲明。

  1. var m 
  2. var name 

而以下代碼包含函數調用和初始化賦值,最終要被編譯為機器指令,并且需要在main函數之前執行;這些指令最終必須占用一塊存儲空間并且能夠加載到內存中。

  1. var m = map[string]int
  2.     "Jack": 18, 
  3.     "Rose": 16, 
  4.  
  5. var name = flag.String("name""""user name"

它們被存儲在可執行文件的什么地方了呢?

通過逆向分析,發現Go編譯器合并了函數外的代碼調用(全局變量的初始化賦值),自動生成了一個 init 函數;很明顯,在func_init.go源文件中并沒有定義初始化函數。

這可能也是編譯器重命名自定義init函數的原因吧。

編譯存儲

所有的初始化函數都不可被直接調用!所有它們會被存儲起來并在程序啟動時自動執行。

在代碼編譯過程中,當前包的初始化函數及其依賴的包的初始化,會被存儲到一個特殊的結構體中,該結構體定義在runtime/proc.go源文件中,如下所示:

  1. type initTask struct { 
  2.     state uintptr // 當前包在程序運行時的初始化狀態:0 = uninitialized, 1 = in progress, 2 = done 
  3.     ndeps uintptr // 當前包的依賴包的數量 
  4.     nfns  uintptr // 當前包的初始化函數數量 

Go語言是一個語法糖很重的編程語言,在源碼中看到的往往不是真實的。

runtime.initTask結構體是一個編譯時可修改的動態結構。其真實面貌如下所示:

  1. type initTask struct { 
  2.     state uintptr // 當前包在程序運行時的初始化狀態:0 = uninitialized, 1 = in progress, 2 = done 
  3.     ndeps uintptr // 當前包的依賴包的數量 
  4.     nfns  uintptr // 當前包的初始化函數數量 
  5.     deps  [ndeps]*initTask // 當前包的依賴包的initTask指針數組(不是slice) 
  6.     fns   [nfns]func ()    // 當前包的初始化函數指針數組(不是slice) 

每個包的依賴包數量可能不同(ndeps),每個包的初始化函數數量不同(nfns),所以最終生成的initTask對象大小可能不同。

具體編譯過程參考cmd/compile/internal/gc/init.go源文件中的fninit函數,此處不再贅述。

Go編譯器為每個包生成一個runtime.initTask類型的全局變量,該變量的命名規則為“包名..inittask”,如下所示:

從上圖第三列可以看出,每個包的initTask對象大小不同。具體計算方法如下:

  1. size := (3 + ndeps + nfns) * 8 

初始化過程

在可執行程序啟動的初始化過程中,優先執行runtime包及其依賴包的初始化,然后執行main包及其依賴包的初始化。

一個包可能被多個包依賴,但是每個包的都只初始化一次,通過runtime.initTask.state字段進行控制。

具體的初始化邏輯請參考runtime/proc.go源文件中的main函數和doInit函數。

在初始化過程中,runtime.doInit函數會被調用很多次,其具體執行流程如本文開頭的“包初始化”一節所述一致。

如前圖所示的func_init.2.go源文件,編譯之后包含兩個初始化函數:一個是編譯器自動生成的,另一個是編譯器重命名的;自動生成的初始化函數優先執行。

如前圖所示的func_init.2.go源文件,編譯之后生成的main..inittask全局變量的內存地址是0x000000000054dc60。我們動態調試runtime.doInit函數,在其參數為main..inittask全局變量指針時暫停執行,觀察參數的數據結構。

從動態調試時展示的內存數據我們反推出如下偽代碼:

  1. package main 
  2.  
  3. var inittask = struct { 
  4.   state uintptr    // 當前包在程序運行時的初始化狀態:0 = uninitialized, 1 = in progress, 2 = done 
  5.   ndeps uintptr    // 當前包依賴的包的initTask數量 
  6.   nfns  uintptr    // 當前包的初始化函數數量 
  7.   deps  [2]uintptr // 當前包依賴的包的initTask指針數組(不是slice) 
  8.   fns   [2]uintptr // 當前包的初始化函數指針數組(不是slice) 
  9. }{ 
  10.   state: 0, 
  11.   ndeps: 2, 
  12.   nfns:  2, 
  13.   deps:  [2]uintptr{0x54ef60, 0x54eca0}, // flag..inittask,fmt..inittask 
  14.   fns:   [2]uintptr{0x4a4ec0, 0x4a4d60}, // main.init,main.init.0 

在func_init.2.go源文件中,引用了flag、fmt兩個包,所以main包的初始化必須在這兩個包的初始化完成之后執行。

  1. import "flag" 
  2. import "fmt" 

通常initTask.ndeps字段的值與import的數量相同。

編譯器自動生成的init函數先于代碼源文件中自定義的init函數執行。

結語

至此,本文完整地、詳細地介紹了Go中關于初始化函數相關的內容。

相信在認真刨析了初始化函數的所有細節之后,對Go有了更近一步的了解。

希望有助于減少開發編碼過程中的疑惑,更加得心應手,游刃有余。

本文轉載自微信公眾號「Golang In Memory」

 

責任編輯:姜華 來源: Golang In Memory
相關推薦

2023-11-06 08:16:19

APM系統運維

2022-11-11 19:09:13

架構

2019-08-06 09:00:00

JavaScript函數式編程前端

2023-11-20 08:18:49

Netty服務器

2023-10-27 08:15:45

2023-11-08 08:15:48

服務監控Zipkin

2022-02-24 07:34:10

SSL協議加密

2024-03-26 00:17:51

Go語言IO

2022-09-29 13:09:38

DataClassPython代碼

2022-04-28 09:22:46

Vue灰度發布代碼

2025-01-15 09:06:57

servlet服務器Java

2020-10-08 14:32:57

大數據工具技術

2020-02-02 15:14:24

HTTP黑科技前端

2024-04-26 00:01:00

Go語言類型

2024-05-07 08:49:36

Hadoop數據存儲-分布式存儲

2024-05-27 00:00:00

.NET游戲引擎C#

2023-05-17 11:33:45

梯度下降機器學習

2024-02-04 09:44:41

量子計算量子量子物理

2022-03-14 08:01:06

LRU算法線程池

2018-10-22 08:14:04

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕第十五页 | av一区二区三区 | 日韩一区二区三区四区五区六区 | 久久亚洲欧美日韩精品专区 | 国产一级一片免费播放 | 91毛片在线看| 色综合网站 | 欧美成人h版在线观看 | 亚洲精品国产第一综合99久久 | 国产精品欧美一区二区 | 欧美激情久久久 | 一二三区av | 毛片国产 | 99精品久久久久 | 网站黄色在线免费观看 | 久久精品成人 | 四虎在线视频 | 久草网站 | 国产精品美女久久久久久免费 | 欧美视频一级 | 极品国产视频 | 一区二区国产在线观看 | 一区二区三区视频在线免费观看 | 国产日韩欧美一区二区 | 祝你幸福电影在线观看 | 婷婷毛片| 日韩精品在线观看视频 | 一区二区av在线 | 国产99久久精品一区二区永久免费 | 99在线播放 | 国产精品一区久久久久 | 国产精品毛片在线 | 日韩久久成人 | 久久久久久国产精品mv | 国产91丝袜在线熟 | 7777精品伊人久久精品影视 | 9久9久| 国产99视频精品免费播放照片 | 一区二区三区国产精品 | 国产偷录视频叫床高潮对白 | 国产精品美女久久久久久久久久久 |