--- tags: 你所不知道的 C 語言, 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 前置處理器、物件導向程式設計 contributed by <`RusselCK` > ###### tags: `RusselCK` ## [前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor?type=view) * 回顧 C99/C11 的 macro 特徵,探討 C11 新的關鍵字 **_Generic** 搭配 macro 來達到 C++ template 的作用 ### Preprocessor 是後來才納入 C 語言的特徵 * 由 Dennis M. Ritchie (以下簡稱 dmr) 開發的[早期 C 語言編譯器](https://www.bell-labs.com/usr/dmr/www/primevalC.html) 沒有明確要求 function prototype 的順序。 * C preprocessor 以獨立程式的形式存在,所以當我們用 gcc 或 cl (Microsoft 開發工具裡頭的 C 編譯器) 編譯給定的 C 程式時,會呼叫 cpp (伴隨在 gcc 專案的 C preprocessor) 一類的程式 * 先行展開巨集 (macro) 或施加條件編譯等操作 * 再來 才會出動真正的 C 語言編譯器 (在 gcc 中叫做 cc1)。 * 無論原始程式碼有幾個檔案, * 在編譯前,先用 cat 一類的工具,將檔案串接為單一檔案, * 再來執行 “cc” 以便輸出對應的組合語言, * 之後就可透過 assembler (組譯器,在 UNIX 稱為 “as”) 轉換為目標碼, * 搭配 linker (在 UNIX 稱為 “ld”) 則輸出執行擋。 ![](https://i.imgur.com/zhIK759.png) * 你或許會好奇,function prototype 的規範有什麼好處呢? * 這就要從 "[Rationale for International Standard -- Programming Languages -- C](http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf)" 閱讀起,依據 C9X RATIONALE 的第 70 頁 (PDF 檔案對應於第 78 頁) * 提到以下的解說範例: ```cpp extern int compare(const char *string1, const char *string2); void func2(int x) { char *str1, *str2; // ... x = compare(str1, str2); // ... } ``` * 編譯器裡頭的最佳化階段 (optimizer) 可從 function prototype 得知 * 傳遞給函式 compare 的兩個指標型態參數,由於明確標注了 "`const`" 修飾子 * ==指標所指向的內容不可變更== * 所以僅有記憶體地址的使用並讀取相對應的內容 * 但不會因為修改指標所指向的記憶體內容,從而沒有產生副作用 (side effect) * 這樣編譯器可有更大的最佳化空間 * 使得編譯時期,就能進行有效的錯誤分析和偵測 ### 開發物件導向程式時,善用 preprocessor 可大幅簡化開發 (code generater) - [ ] [圖解 JavaScript 的物件模型](https://www.facebook.com/JservFans/photos/pcb.886519028141099/886518218141180/) :::info * ==`#`: [Stringification/Stringizing (字串化)](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html)==: 讓一個表示式變成字串 * 在 [`assert`](http://man7.org/linux/man-pages/man3/assert.3.html) 巨集用到 * ==`##`: [concatenation (連結,接續)](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html)== ::: ```c= struct command { char *name; void (*function) (void); }; #define COMMAND(NAME) { #NAME, NAME ## _command } struct command commands[] = { COMMAND (quit), COMMAND (help), … }; ``` * `#9~14` 在 macro 展開後的樣子 : ```c=9 struct command commands[] = { { "quit", quit_command }, { "help", help_command }, … }; ``` <img style="display: block; margin: auto;" src="https://i.imgur.com/JMOk7Mz.png"></img> * 以 [raytracing](https://github.com/embedded2016/raytracing) (光影追蹤) 程式為例 [ [source](http://wiki.csie.ncku.edu.tw/embedded/2016q1h2) ],考慮以下 macro ([objects.h](https://github.com/embedded2016/raytracing/blob/master/objects.h)): ```cpp #define DECLARE_OBJECT(name) \ struct __##name##_node; \ typedef struct __##name##_node *name##_node; \ struct __##name##_node { \ name element; \ name##_node next; \ }; \ void append_##name(const name *X, name##_node *list); \ void delete_##name##_list(name##_node *list); DECLARE_OBJECT(light) DECLARE_OBJECT(rectangular) DECLARE_OBJECT(sphere) ``` light 在 `DECLARE_OBJECT(light)` 中會取代 name,因此會產生以下程式碼: ```cpp struct __light_node; typedef struct __light_node *light_node; struct __light_node { light element; light_node next; }; void append_light(const light *X, light_node *list); void delete_light_list(light_node *list); ``` * 可用 `gcc -E -P` 觀察輸出 ### _Generic [C11] - [C11 standard (ISO/IEC 9899:2011)](http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853): 6.5.1.1 Generic selection (page: 78-79) * 求開立方根 ([cube root](https://en.wikipedia.org/wiki/Cube_root)) 的程式 (generic.c) 展示了 _Generic 的使用: ```c #include <stdio.h> #include <math.h> #define cbrt(X) \ _Generic((X), \ long double: cbrtl, \ default: cbrt, \ const float: cbrtf, \ float: cbrtf \ )(X) int main(void) { double x = 8.0; const float y = 3.375; printf("cbrt(8.0) = %f\n", cbrt(x)); printf("cbrtf(3.375) = %f\n", cbrt(y)); } ``` 編譯並執行: ```shell $ gcc -std=c11 -o generic generic.c -lm $ ./generic cbrt(8.0) = 2.000000 cbrtf(3.375) = 1.500000 ``` :::info * `<math.h>` 的原型宣告: ```cpp double cbrt(double x); float cbrtf(float x); ``` ::: * C11 程式碼: ```cpp #include <stdio.h> void funci(int x) { printf("func value = %d\n", x); } void funcc(char c) { printf("func char = %c\n", c); } void funcdef(double v) { printf("Def func's value = %lf\n", v); } #define func(X) \ _Generic((X), \ int: funci, char: funcc, default: funcdef \ )(X) int main() { func(1); func('a'); func(1.3); return 0; } ``` 輸出結果是 ``` func value = 1 func value = 97 Def func's value = 1.300000 ``` * 延伸閱讀: * [C11 - Generic Selections](http://www.robertgamble.net/2012/01/c11-generic-selections.html) * [Fun with C11 generic selection expression](https://speakerdeck.com/lichray/fun-with-c11-generic-selection-expression) * [Experimenting with _Generic() for parametric constness in C11](http://fanf.livejournal.com/144696.html) ### 案例 老師直播 53:11 ## [物件導向程式設計篇](/@sysprog/c-oop?type=view) (上) ### 先來看範例 1. [bash-oo-framework](https://github.com/niieani/bash-oo-framework): 引入進階的語言特徵到 [GNU bash](https://www.gnu.org/software/bash/) * 物件導向著重於「思維」,語法只是「輔助」 * Doxygen 可掃描給定原始程式碼內容、註解,之後將自動產生程式文件,其中一種輸出就是 HTML,可透過瀏覽器來閱讀文件和程式碼 * 延伸閱讀: [Learning Doxygen](http://wiki.herzbube.ch/index.php/LearningDoxygen) 2. 修改自 [Doxygen](http://www.stack.nl/~dimitri/doxygen/) 的範例: **[doxygen-oop-in-c](https://github.com/jserv/doxygen-oop-in-c)** ```c #include <stdio.h> /** * \file manual.c */ typedef struct Object Object; //!< Object type typedef struct Vehicle Vehicle; //!< Vehicle type typedef struct Car Car; //!< Car type typedef struct Truck Truck; //!< Truck type /*! * Base object class. */ struct Object { int ref; //!< \private Reference count. }; /*! * Increments object reference count by one. * \public \memberof Object */ static Object * objRef(Object *obj); /*! * Decrements object reference count by one. * \public \memberof Object */ static Object * objUnref(Object *obj); /*! * Vehicle class. * \extends Object */ struct Vehicle { Object base; //!< \protected Base class. }; /*! * Starts the vehicle. * \public \memberof Vehicle */ void vehicleStart(Vehicle *obj); /*! * Stops the vehicle. * \public \memberof Vehicle */ void vehicleStop(Vehicle *obj); /*! * Car class. * \extends Vehicle */ struct Car { Vehicle base; //!< \protected Base class. }; /*! * Truck class. * \extends Vehicle */ struct Truck { Vehicle base; //!< \protected Base class. }; /* implementation */ void vehicleStart(Vehicle *obj) { if (obj) printf("%x derived from %x\n", obj, obj->base); } /*! * Main function. * * Ref vehicleStart(), objRef(), objUnref(). */ int main(void) { Car c; vehicleStart((Vehicle*) &c); } ``` ``` $ git clone https://github.com/jserv/doxygen-oop-in-c.git $ cd doxygen-oop-in-c $ make $ make doc ``` 用網頁瀏覽器打開 `doc/html/index.html` 點擊 "Classes" -> "Object" <img style="display: block; margin: auto;" src="https://i.imgur.com/9l7A7le.png "></img> :::info 檔案 [manual.c](https://github.com/jserv/doxygen-oop-in-c/blob/master/manual.c) 定義了物件的繼承 ::: ==「資料 + 動作」包裝成「物件」== ### 物件導向是一種態度 * 只要有心,[Brainf*ck 語言也能作 Object-Oriented Programming](https://github.com/embedded2015/jit-construct/blob/master/progs/oobrain.b) (OOP)! * [Brainfuck](https://zh.wikipedia.org/wiki/Brainfuck) - [ ] [無拘的物件導向](https://www.ithome.com.tw/voice/124477) ### Data Encapsulation * [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)) 1. 一種程式語言的機制,限制直接存取某些物件的部件 2. 一種程式語言的結構體,其將資料和操作該資料的方法綁在一起,提供了便利性 善用 forward declaration 與 function pointer,用 C 語言實現 [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)): ```c= #include <stdio.h> #include <stdlib.h> /* forward declaration (可另外寫在 *.h) */ typedef struct object Object; typedef int (*func_t)(Object *); /* definition (要有細節)*/ struct object { int a, b; func_t add, sub; }; static int add_impl(Object *self) { // method return self->a + self->b; } static int sub_impl(Object *self) { // method return self->a - self->b; } // & : address of // * : value of // indirect access int init_object(Object **self) { // call-by-value if (NULL == (*self = malloc(sizeof(Object)))) return -1; (*self)->a = 0; (*self)->b = 0; (*self)->add = add_impl; (*self)->sub = sub_impl; return 0; } int main(int argc, char *argv[]) { Object *o = NULL; init_object(&o); o->a = 9922; o->b = 5566; printf("add = %d, sub = %d\n", o->add(o), o->sub(o)); return 0; } ``` `#6` : `typedef int (*func_t)(Object *);` * 一個經過 typedef 的 function pointer,參數是 `Object` 的 pointer,回傳值為 `int` 為何要 `Object **self` 呢? * `#32` 的 `o` 是 `Object` 的 pointer,`#33` 的 `&o` 只是將 `o` 的位址傳入' * 假設 `#23` 的 `(Object **self)`改寫為 `(Object *self)`,那麼接下來的所有操作都只會發生在 `o` 上面 * 但 `o` 只是 `Object` 的 pointer,我們真正想操作的是 `*o` 裡面的東西 ### Namespace - [ ] [探尋 C 語言名稱空間](http://www.ithome.com.tw/voice/109166) * [C语言的作用域/namespace分析](https://www.cnblogs.com/dave_cn/archive/2009/11/12/1601424.html) ```c= #include <stdio.h> #include <stdlib.h> //全局的函数名 int x(const int int_a) {return int_a;} //全局的struct名,属于自定义类型名,所以不会跟上面的(int *(const int))x及下面main中的(int *)x冲突 struct x{ int x; //属于struct x的int型x }; //宏定义,会在预编译时进行代码扩展,所以并不会在编译时产生命名冲突 #define x(x) x int main(int argc, char *argv[]) { int *x = calloc(1, sizeof x); //作用域为main的(int *)x; sizeof计算的是(int *)x大小. x: (((struct x *)x)->x) = x(5); //作为label的x独立存在于一个namespace. printf("%p\n", ((struct x *)x)->x); return 0; } ``` `#19`: `x: (((struct x *)x)->x) = x(5);` * 五個x名稱從左而右,分別代表的是 : * 標籤(Label)、struct定義、變數、struct成員與巨集定義 - [ ] [Vala 程式語言入門](http://rocksaying.tw/archives/13551839.html) :::success 有時候,我們可以透過別的程式語言,或者是別人已經寫好的框架,利用其物件導向的好處 由於他們的基底都是 C 語言,因此都能搭配 GNU Toolchain 使用 * [application binary interface, ABI](https://zh.wikipedia.org/wiki/应用二进制接口) ::: * [JNI (Java Native Interface )](https://zh.m.wikipedia.org/zh-tw/Java本地接口) - [ ] [JVM进阶 -- 浅谈JNI](http://zhongmingmao.me/2019/01/12/jvm-advanced-jni/) ### Inheritance and Polymorphism - [ ] [Inheritance and Polymorphism in C](http://www.codeproject.com/Articles/108830/Inheritance-and-Polymorphism-in-C) >By creating a **VTable (virtual Table)** and providing proper access between base and derived objects, we can achieve inheritance and polymorphism in C. The concept of VTable can be implemented by maintaining a table of pointers to functions. For providing access between base and derived objects, we have to maintain the references of derived object in the base class and the reference of the base object in the derived class. - [ ] [C++ 解析 - 虛擬函數 Virtual Function](http://ublearning.blogspot.com/p/virtual-function.html) ```c=26 (*self)->add = add_impl; (*self)->sub = sub_impl; ``` `#26` : Polymorphism (多型) 的基礎 ==物件導向的思維圖==: (**`Person`**) ![](https://i.imgur.com/eatSkbV.png) 程式的演化: * class “Person” in C++: ```cpp //Person.h class Person { private: char* pFirstName; char* pLastName; public: Person(const char* pFirstName, const char* pLastName); //constructor ~Person(); //destructor void displayInfo(); void writeToFile(const char* pFileName); }; ``` > [C++ Access Modifiers](https://www.programiz.com/cpp-programming/access-modifiers) * representing the above class in C ( use structures and function ) ```c //Person.h typedef struct _Person { char* pFirstName; char* pLastName; }Person; new_Person(const char* const pFirstName, const char* const pLastName); //constructor delete_Person(Person* const pPersonObj); //destructor void Person_DisplayInfo(Person* const pPersonObj); void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName); ``` > `_Person` 前面加個 `_` 代表 private (書寫慣例) * **encapsulate** the functions defined for the structure `Person` ```c //Person.h typedef struct _Person Person; //declaration of pointers to functions typedef void (*fptrDisplayInfo)(Person*); typedef void (*fptrWriteToFile)( Person*, const char*); typedef void (*fptrDelete)( Person *) ; //Note: In C all the members are by default public. We can achieve //the data hiding (private members), but that method is tricky. //For simplification of this article // we are considering the data members //public only. typedef struct _Person { char* pFName; char* pLName; //interface for function fptrDisplayInfo Display; fptrWriteToFile WriteToFile; fptrDelete Delete; }Person; person* new_Person(const char* const pFirstName, const char* const pLastName); //constructor void delete_Person(Person* const pPersonObj); //destructor void Person_DisplayInfo(Person* const pPersonObj); void Person_WriteToFile(Person* const pPersonObj, const char* pFileName); ``` 使用方式 : * 使用者自己實作函式細節 ```c //Person.c person* new_Person(const char* const pFirstName, const char* const pLastName) { Person* pObj = NULL; //allocating memory pObj = (Person*)malloc(sizeof(Person)); if (pObj == NULL) { return NULL; } pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1)); if (pObj->pFirstName == NULL) { return NULL; } strcpy(pObj->pFirstName, pFirstName); pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1)); if (pObj->pLastName == NULL) { return NULL; } strcpy(pObj->pLastName, pLastName); //Initializing interface for access to functions pObj->Delete = delete_Person; pObj->Display = Person_DisplayInfo; pObj->WriteToFile = Person_WriteToFile; return pObj; } ``` * 實作細節完成後,即可使用 ```c Person* pPersonObj = new_Person("Anjali", "Jaiswal"); //displaying person info pPersonObj->Display(pPersonObj); //writing person info in the persondata.txt file pPersonObj->WriteToFile(pPersonObj, "persondata.txt"); //delete the person object pPersonObj->Delete(pPersonObj); pPersonObj = NULL; ``` 其他的演化: (**Employee**) ![](https://i.imgur.com/hcGtqC0.png) - [ ] [Object-oriented techniques in C](https://dmitryfrank.com/articles/oop_in_c) * [Cyclic redundancy check, CRC](https://zh.m.wikipedia.org/zh-tw/循環冗餘校驗) * [Galois field](https://zh.m.wikipedia.org/wiki/有限域) * UML Tool - [ ] [Application NoteObject-Oriented Programming in C](https://www.state-machine.com/doc/AN_OOP_in_C.pdf) * [Java中this和super的用法总结](https://www.cnblogs.com/hasse/p/5023392.html) ### 複習 C 語言程式設計 :dart: [指標、程式設計技巧篇 筆記](https://hackmd.io/PMiRPO1CQWCjzW8xmOdnyA#%E6%8A%80%E5%B7%A7%E7%AF%87) * **designated initializers** [C99] ```c struct S1 { int i; floatf; int a[2]; }; struct S1 x = { .f = 3.1, .i = 2, .a[1] = 9 }; ``` - C99 [3.14] ***object*** - 在 C 語言的物件就指在執行時期,==資料==儲存的區域,可以明確表示數值的內容 - C99 [6.2.4] ***Storage durations of objects*** - 在 object 的生命週期以內,其存在就意味著有對應的常數記憶體位址。 - 作為 object 操作的「代名詞」(alias) 的 pointer,倘若要在 object 生命週期以外的時機,去取出 pointer 所指向的 object 內含值,是未知的。 > The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime. ```c ptr = malloc(size); free(ptr); *ptr ... // indeteminate behavior ``` - C99 [6.2.5] ***Types*** - **C 語言只有 call-by-value** ,函式的傳遞都涉及到數值 - C/C++ 常見的 **forward declaration** 技巧的原理 > An array type of unknown size is an incomplete type. It is completed, for an identifier of that type, by specifying the size in a later declaration (with internal or external linkage). 要區分 `char []` 和 `char *` > A structure or union type of unknown content is an incomplete type. It is completed, for all declarations of that type, by declaring the same structure or union tag with its defining content later in the same scope. ```c struct GraphicsObject; // 沒有給細部定義 struct GraphicsObject *initGraphics(int width, int height); // 合法 struct GraphicsObject obj; // 不合法 ``` - 看起來三個不相關的術語「陣列」、「函式」,以及「指標」都稱為 derived declarator types > Array, function, and pointer types are collectively called derived declarator types. A declarator type derivation from a type T is the construction of a derived declarator type from T by the application of an array-type, a function-type, or a pointer-type derivation to T. ## [物件導向程式設計篇](/@sysprog/c-oop?type=view) (下) ### 案例探討: [oltk](https://github.com/openmoko/system-test-suite/tree/master/gta02-dm2/src/oltk) 如果我們能為 oltk 設計一組高階的 API (“tk” 就是 toolkit 的意思),然後實做本身可支援 **fbdev (Linux framebuffer device)** 和 X11 (Linux 桌面系統),那就可以確保在開發階段和實際在裝置執行的一致性。 **[ [gr_impl.h](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/gr_impl.h#L30) ]** (描述整個繪圖裝置本身) ```clike= struct gr_backend { const char *name; struct gr *(*open)(const char *, int width, int height, int nonblock); }; extern struct gr_backend gr_x11_backend; extern struct gr_backend gr_fb_backend; ``` 雖然我們看到 x11 與 fb 字樣,顯然表示兩種不同的環境,但有趣的是,根本沒看到具體實做,僅有 struct gr_backend 以及裡頭的 open 與 name,**我們稱 open 為 method / virtual function** (OOP 用語)。而在 oltk 銜接的地方,是這樣作: **[ [gr.c](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/gr.c#L34) ]** ```clike= static const struct gr_backend *backends[] = { &gr_x11_backend, &gr_fb_backend, }; static const int n_backends = sizeof(backends) / sizeof(backends[0]); struct gr *gr_open(const char *dev, int nonblock) { struct gr *gr = NULL; for (int i = 0; i < n_backends; i++) { gr = backends[i]->open(dev, 480, 640, nonblock); if (gr) break; printf("gr_open: backend %d bad\n", i); } return gr; } ``` 在此可見到,oltk 初始化時,會逐一去呼叫 **graphics backend (也可以說 "provider")** 的 open 這個 method,界面都是一樣的,但實際上的平台相依程式碼卻大相逕庭。 值得注意的是,無論是 [gr_fb.c](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/gr_fb.c) 或 [gr_x11.c](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/gr_x11.c),這兩個平台相依的實做,在定義實做本體時,都有 gr 的實例 (instance): ```clike= struct gr_fb { struct gr gr; ... }; struct gr_x11 { struct gr gr; ... }; ``` 以 OOP 術語來看,我們可說 gr_fb 和 gr_x11 **「繼承」(inherit)** 了 gr 無論實做怎麼變動,oltk 大部份的程式碼只需要在意將按鈕、文字方塊、底圖等視覺元件描繪在 `gr_open` 所指向的真正實做中,定義在 gr.h 的界面: **[ [gr.h](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/gr.h#L53) ]** (更底層的描述) ```clike= struct gr { int width; int height; int bytes_per_pixel; int depth; void (*close)(struct gr *); void (*update)(struct gr*, struct gr_rectangle *, int); void (*set_color)(struct gr *, unsigned int, struct gr_rgb *, unsigned int); int (*get_color)(struct gr *, unsigned int); int (*sample)(struct gr* gr, struct gr_sample *); }; ``` * [ARGB_8888 / RGB565 / ARGB4444 / ALPHA_8分别代表什么意思?](https://www.cnblogs.com/leaftime/p/3296819.html) 然後在 [oltk.c](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/oltk.c) 中,如果想變更按鈕視覺元件的顏色,可以這麼實做: **[ [oltk.c](https://github.com/openmoko/system-test-suite/blob/44d72a42d250537dc20e213d54f73237c1a85377/gta02-dm2/src/oltk/oltk.c#L170) ]** ```clike void oltk_button_set_color(struct oltk_button *b, enum oltk_button_state_type state, enum oltk_button_color_type color, int rgb) { struct gr_rgb grrgb; struct gr *gr = b->oltk->gr; grrgb.red = ((rgb >> 16) & 0xff) << 8; grrgb.green = ((rgb >> 8) & 0xff) << 8; grrgb.blue = (rgb & 0xff) << 8; gr->set_color(gr, b->oltk->color_index, &grrgb, 1); ``` 顯然,上述程式呼叫的 “`set_color`”,也是個 method,完全看 gr 這個 instance 指向哪一種平台相依的裝置。 :::warning TODO : 結合動態連結 (`dlopen` 等),實作出一個 plugin * [GStreamer](https://zh.wikipedia.org/wiki/GStreamer) * [GStreamer: open source multimedia framework](https://gstreamer.freedesktop.org/) ::: ### 其他案例 * [Object-Oriented Structures in C](http://allenchou.net/2012/01/object-oriented-structures-in-c/) * [Monk-C](https://github.com/sunpaq/monkc) : a toolkit for OOP in pure C - [ ] ==[OOP in C](https://www.cs.rit.edu/~ats/books/ooc.pdf)== (非常詳盡) * callback = delegate * [dryman/opic - Object Persistence In C](https://github.com/dryman/opic) * 讓還未完成的任務,能夠交接給別人 ### Design Patterns in C :::success ==抽象化==各種常見的問題,並提供抽象化的==解決方案== ::: * [20年前GoF提出的設計模式,對這個時代是否還有指導意義?](http://www.infoq.com/cn/articles/design-patterns-proposed-by-gof-20-years-ago) > 二十年前,軟體設計領域的四位大師 (GoF,「四人幫」,又稱 Gang of Four,即 Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides) 通過著作《**_Design Patterns: Elements of Reusable Object-Oriented Software_**》闡述了設計模式領域的開創性成果 最後一章(遺憾的是,讀者們大多直接將其忽略),他們指出: > 「這本書的實際價值也許還值得商榷。畢竟它並沒有提出任何前所未有的演算法或者程式設計技術。它也沒能給出任何嚴格的系統設計方法或者新的設計開發理論 —— 它只是**對現有設計成果的一種審視**。大家當然可以將其視為一套不錯的教材,但它顯然無法為經驗豐富的物件導向設計人員帶來多少幫助。」 >「人們很容易將模式視為一種解決方案,或者說一種能夠進行實際採納及重複使用的技術。相較之下,人們很難弄清其更為確切的核心 —— 即定義其能夠解決的問題以及在哪些背景下才屬於最佳解決方案。總體來講,大多數人都能弄清他人正在做什麼,卻不太了解這樣做的理由 —— 而所謂『理由』對於模式而言正是其需要解決的主要問題。理解模式的目標同樣重要,因為這能幫助我們選定適合自己的模式,也能幫助我們理解現有系統的設計方式。模式作者必須弄清並定義該模式所能解決的問題,或者至少在將其作為解決方案之後做出進一步思考。」 (第 393 頁) :::success 模式是一套立足於特定背景,且擁有一整套可預測結果的解決方案 ::: 實際案例學習 Design Patterns。事先準備工作: ```shell $ git clone https://github.com/QMonkey/OOC-Design-Pattern.git $ cd OOC-Design-Pattern $ make ``` ### Observer Pattern [ [source](http://teddy-chen-tw.blogspot.tw/2013/08/observer-pattern.html) ] **Name:** Observer **Context:** 你在購物網站上看到一個很熱門且價格超便宜的產品,正當你想要下單購買的時候,你發現這個產品目前「售完補貨中」。 **Problem:** 如何得知商品已到貨? **Force:** * 你可以定期連回這個網站查詢產品是否到貨,但是這樣做真的很浪費時間。 * 為了知道商品是否到貨而搬到購物網站的倉庫去住(緊密耦合),是一件很愚蠢的行為。 * 除了你以外,還有很多人都對這個商品有興趣 * 如果商品到貨而你沒有適時地收到通知,你會很火大 **Solution:** 請購物網站 (Subject) 提供「貨到通知」功能,讓你 (Observer) 可以針對有興趣的產品主動加入 (attach) 到「貨到請通知我」的名單之中。你必須將你的電子郵件地址 (update) 提供給購物網站,以便貨品到貨的時候購物網站可以立即通知 (notify) 你。當你對產品不再有興趣的時候,購物網站也要讓你可以從「貨到請通知我」的名單中移除 (detach)。 ![](https://i.imgur.com/tfZATIn.png) [**[ Observer/include/iobserver.h ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Observer/include/iobserver.h) (檔名開頭的 `i` 字母表示 interface) ```clike typedef struct _IObserved IObserved; typedef struct _IObserver IObserver; struct _IObserved { /* 商品 */ void (*registerObserver)(IObserved *, IObserver *); void (*notifyObservers)(IObserved *); void (*removeObserver)(IObserved *, IObserver *); }; struct _IObserver { /* 顧客 */ void (*handle)(IObserver *); }; ``` [**[ Observer/include/observed.h ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Observer/include/observed.h) ```clike #include "iobserver.h" typedef struct _Observed Observed; struct _Observed { IObserver **observers; // next size_t count; size_t size; union { IObserved; IObserved iobserved; }; }; extern Observed *Observed_construct(void *); extern void Observed_destruct(Observed *); ``` [**[ Observer/include/observer.h ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Observer/include/observer.h) ```clike #include "iobserver.h" typedef struct _Observer Observer; struct _Observer { union { IObserver; IObserver iobserver; }; }; extern Observer *Observer_construct(void *); extern void Observer_destruct(Observer *); ``` * [C語言-struct、union、enum](http://gundambox.github.io/2015/10/30/C語言-struct、union、enum/) Observer 是種被動呼叫的典範,當有份資料會不定期的更新,而許多人都想要最新版的資料時,有兩種做法 : 1. 最簡單的是寫個執行緒不斷去確認,但這致使 CPU 的 busy wait; 2. 另一種做法就是當資料更新時,由資料擁有者去通知大家「嘿!新貨到!」,這時在意的人就會跑過來看,如此模式即是 "**observer**"。 要留意的是,現實生活中要讓跑過來的眾人不要撞在同一個時間點,如果是網路模組也是,可能會造成阻塞。 [**[ Observer/src/main.c ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Observer/src/main.c) ```clike const int OBSERVER_SIZE = 10; int main() { Observed *observed = new (Observed); IObserved *iobserved = &observed->iobserved; Observer *observers[OBSERVER_SIZE]; for (int i = 0; i < OBSERVER_SIZE; ++i) { observers[i] = new (Observer); observed->registerObserver(iobserved, &observers[i]->iobserver); printf("handle: %p\n", &observers[i]->iobserver); } printf("\n"); iobserved->notifyObservers(iobserved); for (int i = 0; i < OBSERVER_SIZE; ++i) delete(Observer, observers[i]); delete(Observed, observed); return 0; } ``` * [new 與 malloc 的區別及使用時注意的問題](https://codertw.com/程式語言/560709/#outline__3) * main loop ### Microsoft Component Object Model, COM - [ ] Word 裡面可以開 Excel 的秘密 - [Component Object Model](https://docs.microsoft.com/en-us/windows/win32/api/_com/) ( [wikipedia](https://zh.wikipedia.org/wiki/组件对象模型) ) * [IUnknown interface (unknwn.h)](https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown) > All other COM interfaces are inherited, directly or indirectly, from **IUnknown**. ### GObject signal and property - [ ] [GTK+ History](https://people.redhat.com/mclasen/Usenix04/notes/x29.html) * GObject 是 GLib 的核心技術,而 GLib 又是 Gtk+ 的重要根基,GNOME 桌面系統建構於 Gtk+ ![](https://i.imgur.com/9tWInkQ.png) * [GNU Image Manipulation Program, GIMP](https://zh.wikipedia.org/wiki/GIMP) * [GLib](https://zh.wikipedia.org/wiki/GLib) * [LXDE](https://zh.wikipedia.org/wiki/LXDE) - [ ] [The GLib/GTK+ Development Platform](https://people.gnome.org/~swilmet/glib-gtk-dev-platform.pdf) 第 50、51 頁 : > A GObject class can emit **signals**. >An example of a signal is when the user clicks on a button. The application connects a callback function to the signal to perform the desired action when the event occurs. >Another concept of GObject are ***properties***, which is related to signals. > A good example of a property is the state of a check button, i.e. a boolean value describing whether the button is currently checked or not. When the state changes, the "notify" signal is sent. > In fact, creating a GObject signal or property is a nice way to implement the Observer design pattern; that is, one or several objects observing state changes of another object, by connecting function callbacks. The object emitting the signal is not aware of which objects receive the signal. GObject just keeps track of the list of callbacks to call. So adding a signal permits to decouple classes ### Strategy Pattern [ [source](http://teddy-chen-tw.blogspot.tw/2013/08/strategy-pattern.html) ] **Name:** Strategy **Context:** 為了達到相同的目的,物件可以**因地制宜**,讓行為擁有多種不同的實作方法。例如,一個壓縮檔案物件,可以採用 zip、arj、rar、tar、7z 等不同的演算法來執行壓縮工作。 **Problem:** ==如何讓物件自由切換演算法或行為實作?== **Force:** * 你可以把所有的演算法全部寫進同一個物件,然後用條件式判斷來選用所要執行的版本,但是: * 物件的程式碼很容易變得過於複雜與肥大,不好理解與修改。 * 物件占用過多的記憶體空間,因為可能不會使用到全部的演算法。 * 擴充新的演算法必須要修改既有的程式碼。 * 不容易分別開發、修改與測試每一個演算法。 * 你可以透過繼承,讓子類被重新定義自己的演算法。但是這樣會產生許多類似的類別,但僅僅只有行為上些微的差別。 **Solution:** ==將演算法從物件 (Context) 身上抽離出來。== 為演算法定義一個 Strategy 介面,針對每一種演算法,新增一個實作 Strategy 介面的 ConcreteStrategy 物件。把物件中原本的演算法實作程式碼移到相對應的 ConcreteStrategy 物件身上。讓 Context 物件擁有一個指向 Strategy 介面的成員變數,在執行期間藉由改變 Strategy 成員變數所指向的 ConcreteStrategy 物件,來切換不同的演算法演算法。 <img style="display: block; margin: auto;" src="https://i.imgur.com/QYYYyUB.png"></img> [**[ Strategy/include/itravel_strategy.h ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Strategy/include/itravel_strategy.h) ```clike typedef struct _ITravelStrategy ITravelStrategy; struct _ITravelStrategy { void (*travel)(ITravelStrategy *); }; ``` [**[ Strategy/include/person.h ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Strategy/include/person.h) ```clike typedef struct _Person Person; struct _Person { ITravelStrategy* travelStrategy; void (*setTravelStrategy)(Person*, ITravelStrategy*); void (*travel)(Person*); }; extern Person *Person_construct(void *, ITravelStrategy *); extern void Person_destruct(Person *); ``` [**[ Strategy/src/main.c ]**](https://github.com/QMonkey/OOC-Design-Pattern/blob/master/Strategy/src/main.c) ```clike #include "base.h" #include "itravel_strategy.h" #include "airplane_strategy.h" #include "train_strategy.h" #include "person.h" int main() { TrainStrategy *trainStrategy = new (TrainStrategy); ITravelStrategy *itravelStrategy = &trainStrategy->itravelStrategy; Person* person = new (Person, itravelStrategy); person->travel(person); AirplaneStrategy *airplaneStrategy = new (AirplaneStrategy); itravelStrategy = &airplaneStrategy->itravelStrategy; person->setTravelStrategy(person, itravelStrategy); person->travel(person); delete (Person, person); delete (AirplaneStrategy, airplaneStrategy); delete (TrainStrategy, trainStrategy); return 0; } ``` Strategy pattern 的重要範本,很多 pattern 其實是從 Strategy pattern 延伸,基本概念是:**當一件事情有很多種做法的時候,可在執行時期選擇不同的作法,而非透過 if-else 去列舉,這樣能讓程式碼更容易維護、增修功能。** ### Object-Oriented Programming in Linux Kernel - [ ] 《[Beautiful Code](https://vample.com/ebooks/OReilly.Beautiful.Code.Jun.2007.pdf)》 - [ ] [Object-oriented design patterns in the kernel (1)](http://lwn.net/Articles/444910/) * [`struct fb_ops`](http://lxr.linux.no/linux+v2.6.39/include/linux/fb.h#L623) (Frame Buffer) * [`fb_info`](https://elixir.bootlin.com/linux/latest/ident/fb_info) * [`struct rfkill_ops`](http://lxr.linux.no/#linux+v2.6.39/include/linux/rfkill.h#L145) (Radio Frequency) * [RFKill](https://access.redhat.com/documentation/zh-tw/red_hat_enterprise_linux/6/html/power_management_guide/rfkill) - [ ] [Object-oriented design patterns in the kernel (2)](http://lwn.net/Articles/446317/) ### 案例分析: 通訊系統的可擴充界面 [ [source](http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c) ] 定義一個通用的通訊界面: ```clike typedef struct { int (*open)(void *self, char *fspec); int (*close)(void *self); int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz); int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz); } tCommClass; tCommClass commRs232; /* RS-232 communication class */ commRs232.open = &rs232Open; commRs232.write = &rs232Write; tCommClass commTcp; /* TCP communication class */ commTcp.open = &tcpOpen; commTcp.write = &tcpWrite; ``` 對應的通訊實做: **[ TCP subclass ]** ```clike static int tcpOpen (tCommClass *tcp, char *fspec) { printf ("Opening TCP: %s\n", fspec); return 0; } static int tcpInit (tCommClass *tcp) { tcp->open = &tcpOpen; return 0; } ``` 當然你也可以定義 **HTTP subclass** ```clike static int httpOpen (tCommClass *http, char *fspec) { printf ("Opening HTTP: %s\n", fspec); return 0; } static int httpInit (tCommClass *http) { http->open = &httpOpen; return 0; } ``` 測試程式: ```clike int main (void) { int status; tCommClass commTcp, commHttp; // Same ’base’ class but initialized to // different sub-classes. tcpInit (&commTcp); httpInit (&commHttp); // Called in exactly the same manner. status = (commTcp.open)(&commTcp, "bigiron.box.com:5000"); status = (commHttp.open)(&commHttp, "[](http://www.microsoft.com)http://www.microsoft.com"); return 0; } ``` ### 案例分析: Stack ADT (Abstract Data Type) <img style="display: block; margin: auto;" src="https://i.imgur.com/yNw2Xyr.png"></img> [ [source](http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c) ] namespace: * `stack_push(thing *); /* C */` * `stack::push(thing *); // C++` 用 C++ 表示: ```clike class stack { public: stack(); void push(thing *); thing *pop(); static int an_example_entry; private: ... }; ``` 「慣 C」表示: ```clike struct stack { struct stack_type *self; /* Put the stuff that you put after private: here */ }; struct stack_type { void (*construct)(struct stack *self); /**< initialize memory */ struct stack *(*operator_new)(); /**< allocate a new struct to construct */ void (*push)(struct stack *self, thing *t); thing * (*pop)(struct stack *self); int as_an_example_entry; } Stack = { .construct = stack_construct, .operator_new = stack_operator_new, .push = stack_push, .pop = stack_pop }; ``` 使用方式: ```clike struct stack *st = Stack.operator_new(); /* a new stack */ if (!st) { /* Do something about error handling */ } else { stack_push(st, thing0); /* non-virtual call */ Stack.push(st, thing1); // cast *st to a Stack and push st->my_type.push(st, thing2); /* a virtual call */ } ``` ### Prototype-based OO - [ ] [基於類 (Class-Based) 與 基於原型 (Prototype-Based) 語言的比較](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Details_of_the_Object_Model) - [ ] [以 C 語言實做 Javascript 的 prototype 特性](http://blog.linux.org.tw/~jserv/archives/002057.html) ### 案例分析: Server Framework in modern C [server-framework](https://github.com/embedded2016/server-framework) : 用 C99 撰寫、具備物件導向程式設計風格的伺服器開發框架 詳細的看原共筆~