這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。我終於又開始使用 Go 語言編程了。雖然我在前兩年多的時間裡積极參与這個項目,但從 2012 年起,我就基本沒有參加過這個項目。最初,我之所以做出貢獻,是因為我是貝爾實驗室 [Plan 9](http://9p.io/plan9/)(作業系統) 和 [FreeBSD](https://www.freebsd.org/) 的粉絲。我喜歡可用的、基於 csp 的語言,但是 Go 最初的版本只能在 Linux 和 OS X 上運行。那時候我只有 FreeBSD 系統,因此,我將編譯器工具鏈、運行時和標準庫移植到 FreeBSD (有很多調試成果來自 [Russ Cox](https://swtch.com/~rsc/) )。然而,過去我的大部分工作都是在低延遲的系統軟體上,它們大部分是用 C 語言編寫的,自2007年起,我所有的僱主都不再支援 FreeBSD。因為我並沒有真正的機會用 Go 語言去編寫新的軟體,而且我最終也不再對維護一個作業系統的知識感興趣,我只是為了好玩,所以我對 Go 語言的使用和貢獻都被擱置了。現在我在Google工作,我終於有機會用 Go 語言寫代碼了。雖然我仍然喜歡這門語言,但有一些[經驗報告](https://github.com/golang/go/wiki/ExperienceReports),例如風格那樣的東西結果阻止了我在過去的 5~6 年裡使用這門語言,而我現在覺得有些麻煩。在一些同事的建議下,我想我應該至少記錄下其中的一個。我想念的一件事情是 C 語言 `malloc` 那樣的習慣用法。在 C 語言中,分配的記憶體通常是對 `mallocs -family` 函數的調用,它至少給您足夠的記憶體來完成您想要的功能,或者並不給您分配記憶體。慣用文法大致是這樣的:```cT *p = malloc(sizeof *p);```注意,`p (T *)` 的類型只出現一次。這一行代碼利用了它的運算元時的大小,並且顯示了一個指標 —— 這其實是沒有發生的事情,因為操作符 `sizeof` 的結果必須<sup>①</sup>在編譯時間是可識別的。這種程式設計語言而不是文法定義的結果是 `sizeof` 產生了指向對象的大小;它不會變成運行時的東西。C 語言的好處是,如果我改變用 `T` 表示的類型,我只改變聲明或定義中的類型。在 `site(s)` 中不需要做任何更改,指標對象被分配給記憶體配置的結果。上面的樣本很簡單,讓我們考慮一個更複雜的情況,結構成員指向某種類型:```cstruct set {size_t cap;size_t nmemb;int members[];}struct set *set_create(size_t sz){struct set *n = malloc(sizeof *n + (sz * sizeof *n->members));if (n == NULL) {return NULL;}n->members = malloc(sz * sizeof *n->members);if (n->members == NULL) {free(n);return NULL;}n->cap = sz;n->nmemb = 0;return n;}```如果以後我們想要更改 `struct set` 來支援除 int 以外的成員,我們可能會將成員更改為一個union,並添加一些 enum 類型來指定一些我們想要添加的欄位。我們可以在不改變 `set_create` 中的任何代碼的情況下做到這一點。每次我使用 Go 語言建立了一些結構類型,當需要嵌入一些像 slice 和 map 那樣需要分配記憶體的欄位的時候都讓我很抓狂。在 Go 中,我們被迫重複表格達我們想要分配的東西的類型,儘管編譯器熟知這種類型而且類型推斷是符合語言習慣的(試想一下如這樣的運算式 `a:= b` ),我有時不得不深究一下嵌入欄位的類型是什麼。讓我們來看看在建立一個嵌入了 map 的結構體所涉及的內容:```gotype NamedMap struct {name stringm map[string]string}func NewNamedMap(name string) *NamedMap {return &NamedMap{name: name, m: map[string]string{}}}```我們還可以在 `NewNamedMap` 中使用 `make` ,但是仍然保留了`return &NamedMap{name: name, m: make(map[string]string)}` — 再次,重複它的類型。經過深思熟慮的代碼,應該只有一個(額外的)地方需要我們指定類型來分配它,但是當類型改變時,這仍然需要多處改動代碼。當我在做原型的時候,這就會讓我抓狂,而且我還沒有充分考慮到我需要儲存在 map 中的狀態。我發現在很多地方需要自己手動將 `map[string]string` 更改為 `map[string]T`,每次我需要更改多行代碼時,它都會使我感到困擾。有人可能會說,在寫代碼之前,我應該多考慮一下我需要什麼,那樣會更好。但我仍然會反駁說,在項目的生命週期中開發額外的狀態需求並不少見,比如在上面的例子中。隨著時間的推移,系統的約束也可能會發生變化,這樣一種最初非常好的類型最終會變得不可用。在 Go 中,set 結構可能是這樣的:```gotype Set struct {nmemb intcap intmembers []int}func NewSet(sz int) *Set {return &Set{cap: sz, members: []int{}}}```你可能會問為什麼我不只是用一個 slice,答案是這是一個示範這個問題的簡單例子。不管怎樣,我們以後可能想要支援不同類型的 slice,那樣我們又遇到了之前的問題。set 中如果有一個 slice,我們可能可以忽略初始化,假設我們在添加後只從 slice 中讀取。由於形如 map 和 channel 那樣的類型,因為我們必須在使用前進行分配,所以會使得情況更加複雜。那麼在某個地方重複輸入資訊並不罕見。我不知道該如何解決這個問題。對於複合文本,可能可以添加如下文法:```goreturn &Set{cap: sz, members: Set.members{}}```如果你有一個指向 slice 的指標:```goreturn &Set{cap: sz, members: &Set.members{}}```我不知道我是否喜歡這些複合文字文法。在 C 語言中,為支援 `sizeof` 行為而進行的修正感覺更有表現力和明顯:```goreturn &Set{cap: sz, members: make(Set.members)}```但也許這隻是我用 C 語言編程的時間太長了。我不知道改變新的有相似的行為是有用的還是有價值的;我懷疑它的使用是不尋常的。在任何情況下,我都清楚這將減少重構軟體以及編寫新的軟體的開銷。這個問題並不是那麼糟糕,但修複它肯定會讓我覺得更好。在很多情況下,Go已經比 C 和 C++ (我至今無法忍受) 更有表現力了。我認為,如果在語言中添加了對配置類型的推斷的支援,那麼 Go 語言對 C 系統程式員來說就會更有吸引力,因為除了 GC ,他們還有其他堅持的理由。(就我個人而言,我還希望看到一個關於支援系統級並發的更好的事情,但最好是單獨發布。)我編輯了這篇文章,以修複 C 樣本中的一個錯誤。當我最初編寫這個樣本時,`struct set` 沒有使用靈活的數群組成員。Anmol Sethi 寫信詢問這個特性,並指出我錯誤地分配和再次分配給了FAM。我忘記了要刪除那些代碼。嗷.----------------注釋:① : Kate Flavel 提醒我,對於 VLA 類型來說,這不是必須的,因為它是如此的無用,我總是忘記這點。這種類型有它自己的運算式求值。
via: https://9vx.org/post/a-malloc-idiom-in-go/
作者:Devon 譯者:SergeyChang 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
757 次點擊 ∙ 1 贊