not noly go —— 執行軌跡[一]

敖毛毛發表於2021-11-13

前言

學習一下go 語言,也不完全是go,幾乎是所以語言通用的部分,主要在於鞏固一下基礎,幾乎不會涉及到語法相關的東西。

正文

前置內容

說起語言,很多人喜歡談論解釋型語言和編譯型語言,其實對語言談論編譯型還是解釋型語言是沒有意義的,也不知道當時是誰提出這個概念的,是圖啥呢?

c語言也有直譯器(http://www.softintegration.com/),難道就可以把c語言定義為既可以是編譯型也是解釋型語言。

同樣python 難道就不能被編譯嗎? 參考.net平臺,可以編譯成IL語言。同樣c# 也有自己的解析器啊,以此為例的例子很多。

以此為衍生的問題就會有編譯型語言就是要編譯成可執行的語言,然後另外一個人又出來反駁,編譯型可以編譯成另外一種中間語言不一定要是直接可執行的。然後解釋型的爭論又來了,就是解釋一句執行,不需要編譯,然後另外一個人提出。。。。

至此無休無止,所以忘卻這個概念吧,如果有人談論到這個話題,就儘量不要參和,因為這是一個語言開發者都不會關心的問題,人家只關心解決什麼樣的問題或者什麼樣的痛點。

拋開這個話題,那麼有一些概念倒是實打實存在的。

編譯器:

一個編譯器就是一個程式,他可以閱讀某一種語言編寫的程式,並把程式翻譯成一個等價的、用另外一種語言編寫的程式。

這裡面有幾個關鍵字,等價的、另一種語言。其實這就是一個翻譯過程,比如說英文翻譯成中文,那麼是由一種語言轉換為另外一種語言。

那麼這個等價的是什麼意思呢? 其實它指的是這樣的,比如說英文說你好,那麼轉換中文的意思也應該是你好,這就是等價的,這裡而不是完全相等的意思。

為什麼不是完全相等的呢?其實是這樣的,比如說英文的hello,其實是打招呼的意思,而中文的你好也是打招呼的意思,所以是等價的。

同樣在計算機中,翻譯過程中執行邏輯可能不一樣,比如說我們的c = a - b,我們理解的是a=10,b=20,然後c=-10,編寫高階語言的時候一般是這樣理解,人類思維嘛。

但是呢,我們知道計算機中二進位制沒有減法這個概念的,所以呢,計算機的處理邏輯和我們的邏輯不一樣,但是其還是會輸出-10,這其實就是等價的意思,結果相同,並不是說翻譯後邏輯會一致,而是結果會一致。

直譯器:

它並不會通過翻譯的方式生成目標程式。從使用者的角度上看,直譯器直接利用使用者提供的輸入執行源程式中的指定操作。

在使用者輸入對映成為輸入的過程中,由一個編譯器產生的機器語言目標程式比一個直譯器快很多。然而,直譯器的錯誤診斷通常比編譯器更好,因為它是逐條語句執行源程式。

那麼為什麼我們為啥一般不用c 語言和 c++ 去編寫web 響應程式呢?這裡一個講究的是開發效率,還有一個最重要的是影響使用者體驗的是網速,因為網路之間的時間遠大於程式碼執行時間。

java 語言處理器結合了編譯和解釋的過程。一個java程式首先被編譯成一個稱為位元組碼的中間表達形式。然後由一個虛擬機器對得到的位元組碼加以解釋執行。

採用這種方式的好處是可以跨平臺,因為是位元組碼是中間語言,說到底對計算機來說就是文字,可以理解為一篇文章。那麼只要有程式能夠閱讀這篇文章,那麼這篇文章就是被執行了。

這樣使得java 跨平臺了,只要在不同平臺安裝指定jvm,那麼這個jvm相當於閱讀器,那麼位元組碼就可以被執行了,實際上是位元組碼跨平臺了。相同的,為啥c# 可以很容易跨平臺呢?

其實是因為c# 翻譯成IL語言,然後IL語言是在net平臺上執行的,加入net 平臺能夠在linux 上執行,那麼IL就可以在linux上執行,同樣的間接的c# 就跨平臺了。

為了更快的完成輸入到輸入的處理,有些被稱為即時編譯器的java 編譯器在執行中間程式處理輸入的前一刻首先把位元組碼翻譯成機器語言,然後再執行程式。

所以這裡又出現了一個即時編譯的概念,這個可以參考jit,也就是java 中的即時編譯。因為這個非兩句話可以說清,所以暫時略過。

那麼對於c++ 或者 c 語言,以及後面要介紹的go,他們是如何建立一個可執行程式的呢?是不是直接通過編譯器編譯成一個可執行程式呢?

當然不是。

首先其要進行預處理,那麼預處理幹些什麼呢? 看下c 語言的預處理:

預處理是C語言的一個重要功能,它由預處理程式負責完成。

當對一個原始檔進行編譯時,系統將自動引用預處理程式對源程式中的預處理部分作處理,處理完畢自動進入對源程式的編譯。

C語言提供多種預處理功能,主要處理#開始的預編譯指令,如巨集定義(#define)、檔案包含(#include)、條件編譯(#ifdef)等。合理使用預處理功能編寫的程式便於閱讀、修改、移植和除錯,也有利於模組化程式設計。

可能有些人還是有點蒙,那麼直接看其到底做什麼吧。

在整合開發環境中,編譯,連結是同時完成的。其實,C語言編譯器在對原始碼編譯之前,還需要進一步的處理:預編譯。預編譯的主要作用如下:

1、將原始檔中以”include”格式包含的檔案複製到編譯的原始檔中。

2、用實際值替換用“#define”定義的字串。

3、根據“#if”後面的條件決定需要編譯的程式碼。

經過預處理後,然後就可以進行編譯了,編譯後一般目的碼是彙編,因為組合語言比較容易除錯和輸出。

其實是這樣的,機器語言編寫是非常困難的,後面才有了彙編,如果高階語言直接編譯成機器語言,可想而知還得把原來彙編的工作量做一遍,還不如轉成彙編,然後彙編有自己的彙編器進行處理。

我們在轉成彙編後,那麼彙編器會把彙編程式碼轉成可重定位的機器程式碼。注意,這裡是可重位的機器程式碼。

什麼是可重位的機器程式碼?這東西還和作業系統有關。

我們知道在單道程式的時候,使用者還在寫二進位制的時候呢,使用者編寫的實體地址是寫死的。

哎,這就有人問了,我們的程式裡面不都是變數嗎?哪來的固定的實體地址呢。舉一個簡單的例子啊,比如說我們程式要呼叫一個方法。

那麼問題來了,它是如何呼叫的?是啊,憑什麼程式能夠呼叫一個方法呢?它怎麼這麼聰明呢。其實吧,程式呼叫一個方法,相當於執行一條指令。

這個指令包括了跳轉的命令和具體的地址,以及引數。是啊,如果一個方法沒有具體的地址,那麼程式怎麼知道怎麼執行呢,所以可重位的機器程式碼有的方法的物理位置就是確定的了。

這些地址是固定的,但是我們知道程式在執行前是不會知道自己的記憶體位置在什麼地方,因為這是作業系統分配的。

這麼這個可重位的機器程式碼 的意思是這樣的,比如可重位的機器程式碼裡面的地址是180,作業系統是從4000開始給這個程式分配實體地址的,那麼可重位的機器程式碼在執行的時候就會給180加上4000,在4180的物理位置進行操作。

當然這是舉一個例子了,也不一定是180+4000這樣讓程式去算,可能是cpu去搞定。

1、靜態重定位:即在程式裝入記憶體的過程中完成,是指在程式開始執行前,程式中的各個地址有關的項均已完成重定位,地址變換通常是在裝入時一次完成的,以後不再改變,故成為靜態重定位。

2、動態重定位:它不是在程式裝入記憶體時完成的,而是CPU每次訪問記憶體時 由動態地址變換機構(硬體)自動進行把相對地址轉換為絕對地址。動態重定位需要軟體和硬體相互配合完成。

上面這兩個就是重定位方式,第二種裡面是非常複雜的,只需理解什麼是可重位就行。

那麼下面就介紹連結器了。

什麼是連結器呢?

連結器的功能是,將一個或多個編譯器生成的目標檔案及庫連結為一個可執行檔案。

我們知道我們編譯器是編譯的是我們當前開發的模組,並不包含我們引用的庫的編譯,因為其早就編譯好了。

而且我們有沒有發現,其實我們呼叫庫方法的時候,我們看到的是宣告而不是原始碼。這就是讓我們編譯器可以通過,因為編譯器只要檢查宣告符合即可。

但是我們知道只有申明是無法執行的,還需要連結器將其連結起來。雖然這樣說連結器,不太準確,姑且這麼理解,後面獨立系列補充。

那麼編譯器又包括瞭解析器,解析器其包含了詞法分析器、語法分析器:

其主要作用是進行語法分析,提取出句子的結構。廣義來說輸入一般是程式的原始碼,輸出一般是語法樹(syntax tree,也叫parse tree等)或抽象語法樹(abstract syntax tree,AST)。

進一步剝開來,廣義的解析器裡一般會有掃描器(scanner,也叫tokenizer或者lexical analyzer,詞法分析器),以及狹義的解析器(parser,也叫syntax analyzer,語法分析器)。

掃描器的輸入一般是文字,經過詞法分析,輸出是將文字切割為單詞的流。狹義的解析器輸入是單詞的流,經過語法分析,輸出是語法樹或者精簡過的AST。

編譯器還包括了語義分析器、中間程式碼生成器、程式碼優化器、程式碼生成器。

總之,編譯器會將我們的code 轉換成目標語言,像c++和c這種目標語言就是組合語言了。

go 語言相關

說到go語言,那麼其是和c++ 一樣編譯 + 彙編 + 連結器可以生成可執行檔案的語言。

也沒有去看go語言是不是有直譯器哈,但是其主流還是程式設計成二進位制的語言哈,那麼其效率是要比在直譯器上執行是要快一些的。

那麼其如果用go語言去開發http互動應用意義應該不大,因為其主要是網路因素,你說執行效率高也就多臺機器的問題了。

那麼go語言適合做什麼呢? 比較出名的就是docker了,docker 這種肯定不會用java去寫的,因為jvm 就讓人揹負不起。

其應該是替代了原先c++ 和c開發的一些功能,因為其具備垃圾回收,開發起來更加迅速,出錯率也低上一些,且執行效率又比其他基於虛擬機器要高很多。

go 語言開發環境的安裝:

https://www.runoob.com/go/go-environment.html

然後安裝後,看下其安裝的目錄,一般叫做goroot哈。

然後看一下go root下面的bin目錄下,bin目錄下面一般是可執行檔案,一般其實是工具類。

那麼看下這兩個是用來幹什麼的哈。
執行:

go help

上面說這個go.exe 是一個工具用來管理go原始碼的。

可以看到有這樣一些功能哈。

如果想知道具體,那麼可以通過go help 去查詢看哈,看具體的,說多了都是誤導。

讓我奇怪的是有一個gopath的目錄。而且還要一個環境配置,後來我就去查了一下。

我也是一名go初學者,不知道理解的對不對。

它上面說用於解決宣告問題,會不會是可以引用其目錄下的庫檔案的呢?這裡只是猜測,接著往下看。

下面的檔案是go build 生成的。

GOPATH環境變數列出了查詢Go程式碼的位置。

然後gopath 可以是多個,在windows 下面用逗號隔開。

如果環境沒有設定,然後就是在windows 在預設就是當前使用者下的go目錄下。

然後go env GOPATH 可以檢視當前的GOPATH 。

然後給了我們這個 https://golang.org/wiki/SettingGOPATH 告訴我們去看一下怎麼設定哈。

在GOPATH中列出的每個目錄必須有一個規定的結構:

src 儲存原始碼,在src目錄下確定了路徑和可執行檔名。

pkg目錄存放已安裝的包物件。 在Go樹中,每個目標作業系統和 體系結構對有自己的PKG子目錄 (包裹/ GOOS_GOARCH)。

bin目錄儲存已編譯的命令。然後就嘰嘰歪歪舉了一些例子。

Each command is named for its source directory, but only
the final element, not the entire path. That is, the
command with source in DIR/src/foo/quux is installed into
DIR/bin/quux, not DIR/bin/foo/quux. The "foo/" prefix is stripped
so that you can add DIR/bin to your PATH to get at the
installed commands. If the GOBIN environment variable is
set, commands are installed to the directory it names instead
of DIR/bin. GOBIN must be an absolute path.

估計就是可以專案直接互相引用的。因為還沒用到,那麼暫且知道這樣一個概念,等用到了估計就清晰了。

持續更新,一週一到兩節,學習記錄。下一節命名原始碼檔案。

相關文章