成人免费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-20 08:18:49

Netty服務器

2023-11-06 08:16:19

APM系統運維

2022-11-11 19:09:13

架構

2019-08-06 09:00:00

JavaScript函數式編程前端

2023-10-27 08:15:45

2022-02-24 07:34:10

SSL協議加密

2023-11-08 08:15:48

服務監控Zipkin

2020-02-02 15:14:24

HTTP黑科技前端

2024-03-26 00:17:51

Go語言IO

2022-04-28 09:22:46

Vue灰度發布代碼

2022-09-29 13:09:38

DataClassPython代碼

2025-01-15 09:06:57

servlet服務器Java

2020-10-08 14:32:57

大數據工具技術

2023-03-31 08:16:53

Flutter優化內存管理

2018-10-22 08:14:04

2019-07-04 15:16:52

數據挖掘大數據算法

2022-02-18 10:13:07

SolrElasticSea開源

2023-12-06 16:28:56

2022-09-06 11:21:49

光網絡光纖

2024-04-26 00:01:00

Go語言類型
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日本一区高清 | 久久国色 | 国产日韩欧美 | 日韩免费网 | 天堂久久av| 亚洲成av | 久久国产精品视频 | 天天艹逼网 | 久久精品小视频 | 99re视频在线免费观看 | 国产一区日韩在线 | 国产精品美女久久久久久免费 | 欧美夜夜| 久久久久久久久国产成人免费 | 99久久免费精品视频 | 成人在线精品视频 | 日屁视频 | 精品欧美一区二区三区久久久小说 | 国产精品一区二区三区四区 | 一区二区三区视频在线免费观看 | 日韩一二区 | 97av视频| 国产99久久久国产精品 | 国产一区二区三区在线观看免费 | 国产欧美在线观看 | 国产三级大片 | 成人午夜免费在线视频 | 欧美视频三区 | 欧美v日韩 | 巨大荫蒂视频欧美另类大 | 国产成人精品一区 | 欧美性受xxxx白人性爽 | 亚洲一区二区视频 | 精品久久久精品 | 人人艹人人 | 欧美6一10sex性hd | 亚洲综合在线视频 | 天天摸天天干 | 欧美一区二区三区在线看 | 欧美 日韩 中文 | 国产一区二区日韩 |