在上一篇文章中,咱們詳細介紹了go語言編譯爲機器碼經歷的:詞法分析 => 語法分析 => 類型檢查 => 中間代碼 => 代碼優化 => 生成機器碼windows
可是在源代碼生成執行程序的過程當中,其實還經歷了連接等過程。總的來講一個程序的生命週期能夠歸納爲: 編寫代碼 => 編譯 => 連接 => 加載到內存 => 執行app
在第5章咱們將對其進行逐一解釋ide
咱們編寫的程序可能會使用其餘程序或程序庫( library ) 正如咱們在helloworld程序中使用的fmt package函數
咱們編寫的程序必須與這些程序或程序庫一塊兒纔可以執行優化
連接是將咱們編寫的程序與咱們須要的外部程序組合在一塊兒的過程
連接器是系統軟件,在系統開發中起着相當重要的做用,由於它能夠進行單獨的編譯。您能夠將它分解爲更小,更易管理的塊,而後分別進行修改和編譯,而不是將一個大型應用程序組織爲一個總體的源文件。當您更改其中一個模塊時,只需從新編譯它並從新連接應用程序,而無需從新編譯其餘源文件。
連接分爲兩種,靜態連接與動態連接
靜態連接的特色在於連接器會將程序中使用的全部庫程序複製到最後的可執行文件中。而動態連接只會在最後的可執行文件中存儲動態連接庫的位置,並在運行時調用。
所以靜態連接要更快,可移植,由於它不須要在運行它的系統上存在該庫。可是在磁盤和內存上佔用更多的空間
連接發生的過程會在兩個地方,一種是靜態連接會在編譯時的最後一步發生,一種是動態連接在程序加載到內存時發生。
下面咱們簡單對比一下靜態連接與動態連接
有時會看到一些比較老的文章說go語言是靜態連接的,但這種說法是不許確的
如今的go語言不只支持靜態連接也支持動態編譯
總的來講,go語言在通常默認狀況下是靜態連接的,可是一些特殊的狀況,例如使用了CGO(即引用了C代碼)的地方,則會使用操做系統的動態連接庫。例如go語言的net/http
包在默認狀況下會應用libpthread
與 libc
的動態連接庫,這種狀況會致使go語言程序虛擬內存的增長(下一文介紹)
go語言也支持在go build
編譯時傳遞參數來指定要生成的連接庫的方式,咱們可使用go help buildmode
命令查看
» go help buildmode jackson@192 -buildmode=archive Build the listed non-main packages into .a files. Packages named main are ignored. -buildmode=c-archive Build the listed main package, plus all packages it imports, into a C archive file. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed. -buildmode=c-shared Build the listed main package, plus all packages it imports, into a C shared library. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed. -buildmode=default Listed main packages are built into executables and listed non-main packages are built into .a files (the default behavior). -buildmode=shared Combine all the listed non-main packages into a single shared library that will be used when building with the -linkshared option. Packages named main are ignored. -buildmode=exe Build the listed main packages and everything they import into executables. Packages not named main are ignored. -buildmode=pie Build the listed main packages and everything they import into position independent executables (PIE). Packages not named main are ignored. -buildmode=plugin Build the listed main packages, plus all packages that they import, into a Go plugin. Packages not named main are ignored.
archive: 將非 main package構建爲 .a 文件. main 包將被忽略。
c-archive: 將 main package構建爲及其導入的全部package構建爲構建到 C 歸檔文件中
c-shared: 將mainpackage構建爲,以及它們導入的全部package構建到C 動態庫中。
shared: 將全部非 main package合併到一個動態庫中,當使用-linkshared參數後,可以使用此動態庫
exe: 將main package和其導入的package構建爲成爲可執行文件
本文再也不介紹go如何手動使用動態庫這一高級功能,讀者只需如今知道go能夠實現這一功能便可
下面咱們以helloworld程序爲例,來講明go語言編譯與連接的過程,咱們可使用go build
命令,-x
參數表明了打印執行的過程
go build -x main.go
輸出以下:
WORK=/var/folders/g2/0l4g444904vbn8wxnrw0j_980000gn/T/go-build757876739 mkdir -p $WORK/b001/ cat >$WORK/b001/importcfg << 'EOF' # internal # import config packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a EOF cd /Users/jackson/go/src/viper/XXX /usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid JqleDuJlC1iLMVADicsQ/JqleDuJlC1iLMVADicsQ -goversion go1.13.6 -D _/Users/jackson/go/src/viper/args -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal cp $WORK/b001/_pkg_.a /Users/jackson/Library/Caches/go-build/cf/cf0dc65f39f01c8494192fa8af14570b445f6a25b762edf0b7258c22d6e10dc8-d # internal cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a packagefile errors=/usr/local/go/pkg/darwin_amd64/errors.a ... EOF mkdir -p $WORK/b001/exe/ cd . /usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=zCU3mCFNeUDzrRM33f4L/JqleDuJlC1iLMVADicsQ/r7xJ7p5GD5T9VONtmxob/zCU3mCFNeUDzrRM33f4L -extld=clang $WORK/b001/_pkg_.a /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal mv $WORK/b001/exe/a.out main rm -r $WORK/b001/
下面咱們對輸出進行逐行分析
建立了一個臨時目錄,用於存放臨時文件。默認狀況下命令結束時自動刪除此目錄,若是須要保留添加-work
參數。
WORK=/var/folders/g2/0l4g444904vbn8wxnrw0j_980000gn/T/go-build757876739 mkdir -p $WORK/b001/ cat >$WORK/b001/importcfg << 'EOF' # internal
生成編譯配置文件,主要爲編譯過程須要的外部依賴(如:引用的其餘包的函數定義)
# import config packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
編譯,生成中間結果$WORK/b001/pkg.a
,
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid JqleDuJlC1iLMVADicsQ/JqleDuJlC1iLMVADicsQ -goversion go1.13.6 -D _/Users/jackson/go/src/viper/args -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go
.a文件由compile命令生成,也能夠經過go tool compile進行調用
.a類型的文件又叫作目標文件(object file),其是一個壓縮包,內部包含了_.PKGDEF`、`_go.o
兩個文件,分別爲編譯目標文件和連接目標文件
$ file _pkg_.a # 檢查文件格式 _pkg_.a: current ar archive # 說明是ar格式的打包文件 $ ar x _pkg_.a #解包文件 $ ls __.PKGDEF _go_.o
文件內容由代碼導出的函數、變量以及引用的其餘包的信息組成。爲了弄清這兩個文件包含的信息須要查看go編譯器實現的相關代碼,相關代碼在src/cmd/compile/internal/gc/obj.go
文件中(源碼中的文件內容可能隨版本更新變化,本系列文章以Go1.13.5版本爲準)
下面代碼中生成ar文件,ar文件 是一種很是簡單的打包文件格式,普遍用於linux中靜態連接庫文件中,文件以 字符串"!\n"
開頭。隨後跟着60字節的文件頭部(包含文件名、修改時間等信息),以後跟着文件內容。由於ar文件格式簡單,Go編譯器直接在函數中實現了ar打包過程。
startArchiveEntry用於預留ar文件頭信息位置(60字節),finishArchiveEntry用於寫入文件頭信息,由於文件頭信息中包含文件大小,在寫入完成以前文件大小未知,因此分兩步完成。
func dumpobj1(outfile string, mode int) { bout, err := bio.Create(outfile) if err != nil { flusherrors() fmt.Printf("can't create %s: %v\n", outfile, err) errorexit() } defer bout.Close() bout.WriteString("!<arch>\n") if mode&modeCompilerObj != 0 { start := startArchiveEntry(bout) dumpCompilerObj(bout) finishArchiveEntry(bout, start, "__.PKGDEF") } if mode&modeLinkerObj != 0 { start := startArchiveEntry(bout) dumpLinkerObj(bout) finishArchiveEntry(bout, start, "_go_.o") } }
生成連接配置文件,主要爲須要連接的其餘依賴
cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a packagefile errors=/usr/local/go/pkg/darwin_amd64/errors.a ... EOF
執行連接器,生成最終可執行文件main
,同時可執行文件會拷貝到當前路徑,最後刪除臨時文件
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=zCU3mCFNeUDzrRM33f4L/JqleDuJlC1iLMVADicsQ/r7xJ7p5GD5T9VONtmxob/zCU3mCFNeUDzrRM33f4L -extld=clang $WORK/b001/_pkg_.a /usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal mv $WORK/b001/exe/a.out main rm -r $WORK/b001/
在本文中,咱們介紹了go程序從源代碼到運行須要經歷的重要一環——連接,並介紹了靜態連接與動態連接
在本文中,咱們用一個例子介紹了編譯與連接的具體過程
在下文中,咱們將介紹go語言的內存分配