1. 程式人生 > >golang初探與命令原始碼分析

golang初探與命令原始碼分析

前段時間有群友在群裡問一個go語言的問題:

就是有一個main.go的main函式裡呼叫了另一個demo.go裡的hello()函式。其中main.go和hello.go同屬於main包。但是在main.go的目錄下執行go run main.go卻報hello函式沒有定義的錯:

程式碼結構如下:

**gopath ---- src**

          **----gohello** 

                **----hello.go** 

                    ** ----main.go**

main.go如下:

package main

import "fmt"

func main() {

 fmt.Println("my name is main")

 hello()
}

hello.go如下:

package main

import "fmt"

func hello() {
 fmt.Println("my name is hello")
}

當時我看了以為是他GOPATH配置的有問題,然後自己也按照這樣試了一下,報同樣的錯,在網上查了,也有兩篇文章是關於這個錯的,也提供瞭解決方法,即用go run main.go hello.go,試了確實是可以的。

雖然是個很簡單的問題,但是也涉及到了go語言底層對於命令列引數的解析。那就來分析一下語言底層的實現吧,看一下底層做了什麼,為什麼報這個錯?

分析:

以下使用到的Go SDK版本為1.8.3

該版本中go支援的基本命令有以下16個:

build       compile packages and dependencies
clean       remove object files
doc         show documentation for package or symbol
env         print Go environment information
bug         start a bug report
fix         run go tool fix on packages
fmt         run gofmt on package sources
generate    generate Go files by processing source
get         download and install packages and dependencies
install     compile and install packages and dependencies
list        list packages
run         compile and run Go program
test        test packages
tool        run specified go tool
version     print Go version
vet         run go tool vet on packages

在Go SDK的src/cmd/go包下有main.go檔案中,Command型別的commands陣列對該16個命令提供了支援:

我們首先知道go語言的初始化流程如下:

在執行main.go中的主函式main之前,對import進來的包按順序初始化,最後初始化main.go中的型別和變數,當初始化到commands陣列時,由於cmdRun定義在於main.go同包下的run.go中,那麼就先去初始化run.go中的變數和init方法,如下程式碼,先把cmdRun初始化為Command型別,然後執行init()函式。

var cmdRun = &Command{
 UsageLine: "run [build flags] [-exec xprog] gofiles... [arguments...]",
 Short:     "compile and run Go program",
 Long: `
Run compiles and runs the main package comprising the named Go source files.
A Go source file is defined to be a file ending in a literal ".go" suffix.

By default, 'go run' runs the compiled binary directly: 'a.out arguments...'.
If the -exec flag is given, 'go run' invokes the binary using xprog:
 'xprog a.out arguments...'.
If the -exec flag is not given, GOOS or GOARCH is different from the system
default, and a program named go_$GOOS_$GOARCH_exec can be found
on the current search path, 'go run' invokes the binary using that program,
for example 'go_nacl_386_exec a.out arguments...'. This allows execution of
cross-compiled programs when a simulator or other execution method is
available.

For more about build flags, see 'go help build'.

See also: go build.
 `,
}

func init() {
 cmdRun.Run = runRun // break init loop

 addBuildFlags(cmdRun)
 cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "")
}

init()中,將runRun(其實型別是一個方法,用於處理run後的引數)賦值給cmdRu.run,addBuildFlags(cmdRun)主要是給run後面增加命令列引數(如:-x是列印其執行過程中用到的所有命令,同時執行它們)。其他15個命令和cmdRun類似,各有各的run實現。

下來主要看main.go中main的這塊程式碼:

for _, cmd := range commands {
   if cmd.Name() == args[0] && cmd.Runnable() {
     cmd.Flag.Usage = func() { cmd.Usage() }
     if cmd.CustomFlags {
       args = args[1:]
     } else {
       cmd.Flag.Parse(args[1:])
       args = cmd.Flag.Args()
     }
     cmd.Run(cmd, args)
     exit()
     return
   }
 }

這塊程式碼遍歷commands陣列,當遍歷到cmdRun時,cmd.Name()其實就是拿到cmdRun.UsageLine的第一個單詞run

就會進入if分支,由於cmd.CustomFlags沒有初始化故為false,走else分支,然後開始解析args命令列引數,args[1:]即取run後的所有引數。然後去執行cmd.Run(cmd, args),對於cmdRun來說,這裡執行的就是run.go中init()的第一行賦值cmdRun.run(上面說了,這是一個函式,不同命令實現方式不同),即去執行run.go中的runRun函式,該函式主要是將命令列引數當檔案去處理,如果是_test為字尾的,即測試檔案,直接報錯。如果是目錄也直接報錯(而且go run後面只能包含一個含main函式的go檔案)。注意到有這麼一行:

p := goFilesPackage(files)

goFilesPackage(files)除了校驗檔案型別和字尾,還會入棧,載入,出棧等操作,由於啟動的時候沒有傳遞hello.go,所以系統載入main.go時找不到hello函式,導致報錯。



本公眾號免費提供csdn下載服務,海量IT學習資源,如果你準備入IT坑,勵志成為優秀的程式猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大資料、面試資料、前端 等資源。同時我們組建了一個技術交流群,裡面有很多大佬,會不定時分享技術文章,如果你想來一起學習提高,可以公眾號後臺回覆【2】,免費邀請加技術交流群互相學習提高,會不定期分享程式設計IT相關資源。


掃碼關注,精彩內容第一時間推給你

相關推薦

golang初探命令原始碼分析

前段時間有群友在群裡問一個go語言的問題: 就是有一個main.go的main函式裡呼叫了另一個demo.go裡的hello()函式。其中main.go和hello.go同屬於main包。但是在main.go的目錄下執行go run main.go卻報hello函式沒有定義的錯: 程式碼結構如下: **g

Golang中heap包原始碼分析

heap的實現使用到了小根堆,下面先對堆做個簡單說明 1. 堆概念     堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於(或不小於)其左孩子和右孩子節點的值。   最大堆和最小堆是二叉堆的兩種形式。   最大堆:根結點的鍵值是所有堆結點鍵值中最大者。   最小

C++ STL 記憶體配置的設計思想關鍵原始碼分析

下面會結合關鍵原始碼分析C++STL(SGI版本)的記憶體配置器設計思想。關鍵詞既然是“思想”,所以重點也就呼之欲出了。 1、allocator的簡短介紹 我閱讀的原始碼是SGI公司的版本,也是看起來最清楚的版本,各種命名最容易讓人看懂。allocator有人叫它空間配置器,因為空間不一定是記憶體,也可以是

C++STL記憶體配置的設計思想關鍵原始碼分析

說明:我認為要讀懂STL中allocator部分的原始碼,並汲取它的思想,至少以下幾點知識你要了解:operator new和operator delete、handler函式以及一點模板知識。否則,下面你很可能看不大明白,補充點知識再學習STL原始碼比較好。 下面會結合關鍵原始碼分析C++STL(SGI版

Shiro認證授權原始碼分析

這裡使用官方提供的demo進行除錯,進入原始碼分析。 public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(

jdk1.8中HashSetLinkedHashSet原始碼分析

注:基於JDK 1.8.0_131原始碼為例進行分析: 一、HashSet分析 1.1 HashSet的實現   HashSet實現set介面,是基於HashMap或者LinkedHashMap實現的。   HashSet中封裝了一個 HashM

24、HashSetHashMap原始碼分析

1、HashSet底層是使用HashMap實現的。當使用add方法將物件新增到set當中時,實際上是將該物件作為底層所維護的Map物件的Key,,而value則都是同一個Object物件(該物件我們用不上); HashSet部分原始碼: private transient

Farneback 光流演算法詳解 calcOpticalFlowFarneback 原始碼分析

光流基礎概念 真實的三維空間中,描述物體運動狀態的物理概念是運動場。三維空間中的每一個點,經過某段時間的運動之後會到達一個新的位置,而這個位移過程可以用運動場來描述。 而在計算機視覺的空間中,計算機所接收到的訊號往往是二維圖片資訊。由於缺少了一個維度的資訊,

redis原始碼分析思考(十七)——有序集合型別的命令實現(t_zset.c)

    有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令: 有序集合命令 命令 對應操作 時

redis原始碼分析思考(十六)——集合型別的命令實現(t_set.c)

    集合型別是用來儲存多個字串的,與列表型別不一樣,集合中不允許有重複的元素,也不能以索引的方式來通過下標獲取值,集合中的元素還是無序的。在普通的集合上增刪查改外,集合型別還實現了多個集合的取交集、並集、差集,集合的命令如下表所示: 集合命

redis原始碼分析思考(十五)——雜湊型別的命令實現(t_hash.c)

    雜湊型別又叫做字典,在redis中,雜湊型別本身是一個鍵值對,而雜湊型別裡面也存貯著鍵值對,其對應關係是,每個雜湊型別的值對應著一個鍵值對或多對鍵值對,如圖所示: 雜湊型別命令 命令 對應操

redis原始碼分析思考(十四)——列表型別的命令實現(t_list.c)

    列表型別是用來存貯多個字串物件的結構。一個列表可以存貯232-1個元素,可以對列表兩端進行插入(push)、彈出(pop),還可以獲取指定範圍內的元素列表、獲取指定索引的元素等等,它可以靈活的充當棧和佇列的角色。下面列出列表的命令: 列

redis原始碼分析思考(十三)——字串型別的命令實現(t_string.c)

    在對字串操作的命令中,主要有增加刪查該、批處理操作以及編碼的轉換命令,現在列出對字串物件操作的主要常用命令: 常用命令表 命令 對應操作 時間複雜度

redis原始碼分析思考(十七)——有序集合型別的命令實現(t_set.c)

    有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令: 有序集合命令 命令 對應操作 時間複

netty原始碼分析(二十一)Netty資料容器ByteBuf底層資料結構深度剖析ReferenceCounted初探

ByteBuf ByteBuf是Netty提供的代替jdk的ByteBuffer的一個容器,首先看一下他的具體用法: public class ByteBufTest0 { public static void main(String[] args) {

FFmpeg libswscale原始碼分析2-轉碼命令濾鏡圖

本文為作者原創,轉載請註明出處: libswscale 原始碼分析系列文章: [1]. [FFmpeg libswscale原始碼分析1-API介紹](https://www.cnblogs.com/leisure_chn/p/14349382.html) [2]. [FFmpeg libsws

AndroidJS之JsBridge使用原始碼分析

在Android開發中,由於Native開發的成本較高,H5頁面的開發更靈活,修改成本更低,因此前端網頁JavaScript(下面簡稱JS)與Java之間的互相呼叫越來越常見。 JsBridge就是一個簡化Android與JS通訊的框架,原始碼:https://github.com/lzyzsd

OpenCV學習筆記(31)KAZE 演算法原理原始碼分析(五)KAZE的原始碼優化及SIFT的比較

  KAZE系列筆記: 1.  OpenCV學習筆記(27)KAZE 演算法原理與原始碼分析(一)非線性擴散濾波 2.  OpenCV學習筆記(28)KAZE 演算法原理與原始碼分析(二)非線性尺度空間構建 3.  Op

OpenCV學習筆記(30)KAZE 演算法原理原始碼分析(四)KAZE特徵的效能分析比較

      KAZE系列筆記: 1.  OpenCV學習筆記(27)KAZE 演算法原理與原始碼分析(一)非線性擴散濾波 2.  OpenCV學習筆記(28)KAZE 演算法原理與原始碼分析(二)非線性尺度空間構

GCC原始碼分析(一)——介紹安裝

原文連結:http://blog.csdn.net/sonicling/article/details/6702031     上半年一直在做有關GCC和LD的專案,到現在還沒做完。最近幾天程式設計的那臺電腦壞了,所以趁此間隙寫一點相關的分析和