Copyright (慣C) 2017, 2019 宅色夫
使用方式如下:
這有什麼問題呢?
這樣的好處是:
Matrix m
,可以很容易指定特定的方法 (method),藉由 designated initializers,在物件初始化的時候,就指定對應的實作,日後要變更 (polymorphism) 也很方便
equal
和 mul
函式實作時都標註 static
,所以只要特定實作存在獨立的 C 原始程式檔案中,就不會跟其他 compilation unit 定義的符號 (symbol) 有所影響,最終僅有 API gateway 需要公開揭露:girl: Prefer to return a value rather than modifying pointers
This encourages immutability, cultivates pure functions, and makes things simpler and easier to understand. It also improves safety by eliminating the possibility of a NULL argument.
:-1: unnecessary mutation (probably), and unsafe
:+1: immutability rocks, pure and safe functions everywhere
不過仍是不夠好,因為:
values
欄位仍需要公開,我們就無法隱藏實作時期究竟用 float
, double
, 甚至是其他自訂的資料表達方式 (如 Fixed-point arithmetic)m.mul()
就注定會失敗matrixA * matrixB
,對應程式碼為 matO = matA.mul(matB)
,但在上述程式碼中,我們必須寫為 Matrix o = m.mul(m, n)
,後者較不直覺2.
,如果初始化時配置記憶體,那要如何確保釋放物件時,相關的記憶體也會跟著釋放呢?若沒有充分處理,就會遇到 memory leaks延伸閱讀:
也可變化如下:
前述矩陣操作的程式,我們期望能導入下方這樣自動的處理方式:
其中 nalloc
和 nfree
是我們預期的自動管理機制,對應的實作可見 nalloc
複製字串可用 strdup 函式:
strdup 函式會呼叫 malloc 來配置足夠長度的記憶體,當然,你需要適時呼叫 free 以釋放資源。 [heap]
strdupa 函式透過 alloca 函式來配置記憶體,後者存在 [stack],而非 heap,當函式返回時,整個 stack 空間就會自動釋放,不需要呼叫 free。
alloca
function is not in POSIX.1.alloca()
在不同軟硬體平台的落差可能很大,在 Linux man-page 特別強調以下:RETURN VALUE
The alloca() function returns a pointer to the beginning of the allocated space. If the allocation causes stack overflow, program behaviour is undefined.
延伸閱讀:
在 C++11 的 STL,針對使用需求的不同,提供了三種不同的 smart pointer,分別是:
unique_ptr
unique_ptr
物件管理的 smart pointer;當 unique_ptr
物件消失時,就會自動釋放資源。shared_ptr
shared_ptr
共用一份資源的 smart pointer,內部會記錄這份資源被引用的次數(reference counter),只要還有引用該資源的 shared_ptr
物件存在、資源就不會釋放;只有當所有引用這份資源的 shared_ptr
物件都消失的時候,資源才會自動釋放。weak_ptr
shared_ptr
使用的 smart pointer,和 shared_ptr
的不同點在於 weak_ptr
不會增加資源的引用計數,也就是說的 weak_ptr
存在與否不影響資源會不會被釋放掉。這些 smart pointer 都是 class template 的形式,所以適用範圍很廣泛;它們都是被定義在 <memory>
標頭檔、在 std
這個 namespace 下。
Implementing smart pointers for C
Smart pointers for the (GNU) C: Allocating a smart array and printing its contents before destruction:
__attribute__((cleanup))
。但函式回傳的 unbound 物件及函式參數屬性皆無支援 __attribute__((cleanup))
。使用案例:
Randy Meyers (chair of J11, the ANSI C committee) 的文章 The New C:Why Variable Length Arrays?,副標題是 "C meets Fortran, at long last."
一個特例是 Arrays of Length Zero,GNU C 支援,在 Linux 核心出現多次
延伸閱讀: Zero size arrays in C
-fplan9-extensions
可支援 Plan 9 C Compilers 特有功能-fms-extensions
編譯選項。見 GCC Unnamed Fields&t->S
或 type cast (S*)t
。但若用 transparent union,即可透過更漂亮的語法來實作:Cello 在 C 語言的基礎上,提供以下進階特徵:
可寫出以下風格的 C 程式:
typeof 允許我們傳入一個變數,代表的會是該變數的型態。舉例來說:
typeof
大多用在定義巨集上,因為在巨集裏面我們沒辦法知道參數的型態,在需要宣告相同型態的變數時,typeof
會是一個很好的幫手。
以 max 巨集為例:
至於為什麼我們需要將 max 的巨集寫成這樣的形式呢?為何不可簡單寫為 #define max(a,b) (a > b ? a : b)
呢?
這樣的寫法會導致 double evaluation 的問題,顧名思義就是會有某些東西被執行 (evaluate) 過兩次。
試想如下情況:
實際執行後,我們會發現程式輸出竟有3 次 doOneTime
函式,但在 max
的使用,我們只期待會呼叫 2 次?
這是因為在巨集展開後,原本 max(f1(), f2())
會被改成這樣的形式
為了避免這個問題,我們必須在巨集中先用變數把可能傳入的函式回傳值儲存下來,之後判斷就不要再使用一開始傳入的函式,而是是用後來宣告的回傳值變數。
一步步拆解。首先看到的是 __extension__
,是一個修飾字,用來防止 gcc 編譯器產生警告。
什麼情況下,我們會想編譯器產生的警告?在編譯階段,編譯器可能會提醒我們,程式使用到非 ANSI C 標準的語句,我們開發的程式在現在的編譯器可能可以過,但是用其他的編譯器可能就不會過了。
在這邊,出問題的地方應該是在開頭的 ({})
(braced-group within expression),實際編譯過後我們可以看到這樣的警告訊息
這是 gcc 一個 extension,透過這種寫法可以讓我們的 macro 更安全。
如果不想要看見這個警告,可在最前面加上 __extension__
修飾字。
再來的話看到 __typeof__(((type *) 0)->member) * __pmember
這段
可以看到我們用 ((type*)0)->member
的型態再宣告一個新的指標 __pmember
。
(type*)0
代表的是一個被轉型成 type
型態的 struct,這裡我們不用理會是否為一個合法的位址,所以可以直接寫 0
就好。再來我們讓他指向 member
,因此 __typeof__(((type*)0)->member)
代表的便是 member
的資料型態。
再往下看到 (type *) ((char *) __pmember - offsetof(type, member));
這段比較好解釋,其實就只是把 member 的位址扣除 member 在整個 struct 裡面的偏移後,得到整個 struct 的開頭位址而已。
以圖像的方式會長這樣