# 帶你閱讀一座程式 函式拆解II ###### tags: `libmodbus` Copyright 2021, [月下麒麟](https://hackmd.io/@YMont/note-catalog) --- ## 今日主角: **modbus_set_slave(ctx, SERVER_ID)** **Trace go** Reference: [trace here](https://github.com/stephane/libmodbus/blob/master/tests/unit-test-server.c#L67) ```c= modbus_set_slave(ctx, SERVER_ID); ``` 進行查找其函式**定義** ```c= int modbus_set_slave(modbus_t *ctx, int slave) { if (ctx == NULL) { errno = EINVAL; return -1; } return ctx->backend->set_slave(ctx, slave); } ``` 發現定義僅寫,若指標為空,則不繼續往下執行 另外,回傳的內容為指標(ctx)去取得結構modbus_t的成員backend, 但該成員(backend)其型別是另一結構modbus_backend_t, 繼續指向modbus_backend_t,並欲取得成員set_slave。 到這裡,進入第二層結構定義裡面, ```c= typedef struct _modbus_backend { //... int (*set_slave) (modbus_t *ctx, int slave); //... } modbus_backend_t; ``` 同時,你也會有疑問,那然後呢? ~~抵達世界的盡頭~~ \~.~ ![](https://i.imgur.com/XXi0ME0.png) 別緊張,請善用搜尋~ **modbus_backend_t** ![](https://i.imgur.com/aysGkQj.png) 找出所有有關該結構名稱存在的地方 除了在modbus-private.h 定義處出現外, 分別在**modbus-rtu.c** **modbus-tcp.c**, **關鍵就是利用struct賦予初值,而初值的內容為函式名稱 同時,會將函式名稱套入到 結構定義處** ```c= const modbus_backend_t _modbus_rtu_backend = { //... _modbus_set_slave, //函式名稱 //... }; //=====分隔線,不同位置的程式碼===== int (*set_slave) (modbus_t *ctx, int slave); //將函式名稱套入於此 ``` 這個技巧運用到了結構初值的賦予,與function point --- **小補充:** ```c= const modbus_backend_t _modbus_rtu_backend = { //... _modbus_set_slave, //... }; ``` 那你可能也觀察到 **\_modbus_rtu_backend** 這個struct variable要做什麼用? 對先對**modbus_backend_t**進行搜尋, 會發現到modbus-rtu.c 與 modbus-tcp.c都有它的身影, 這與上一篇文章的初始化有關[帶你閱讀一座程式 函式分解I](https://hackmd.io/INCbn-ZoTfuqDyLt8o9nuQ) ```c= const modbus_backend_t _modbus_tcp_backend = {}; const modbus_backend_t _modbus_rtu_backend = {}; ``` 可以觀察到利用結構modbus_backend_t去創建兩個賦予結構初值的陣列, 並且將該宣告的陣列給予名稱 **\_modbus_tcp_backend** , **\_modbus_rtu_backend** >賦予名稱最主要的意義為,可以讓它被接續使用 進行名稱搜訊 ```c= ctx->backend = &_modbus_tcp_backend; ctx->backend = &_modbus_rtu_backend; ``` 呼應上一篇,就是讓此處的初始化指向該陣列。 --- 鏡頭拉回來,進行\_modbus_set_slave的解析 ```c= static int _modbus_set_slave(modbus_t *ctx, int slave) { /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ if (slave >= 0 && slave <= 247) { ctx->slave = slave; } else { errno = EINVAL; return -1; } return 0; } ``` 判斷slave號碼是介於0~247的範圍, 這部分可以參考Modbus Serial Specification裏頭有載名。 ![](https://i.imgur.com/oyDY3WS.png) --- ## 今日配角: header_length, checksum_length, max_adu_length 這三個結構成員出現在**modbus_backend_t**結構裡面。 會發現到不是還有一個backend_type嗎? 為何沒放進來? 因為該成員變數僅用來區分tcp, rtu。 那要介紹這三個主要為跟Modbus Protocol有關 ```c= /*RTU*/ _MODBUS_BACKEND_TYPE_RTU, //0 _MODBUS_RTU_HEADER_LENGTH, //1 -->server address (slave address) _MODBUS_RTU_CHECKSUM_LENGTH, //2 MODBUS_RTU_MAX_ADU_LENGTH, //256 //=====分隔線,不同位置的程式碼===== /*TCP*/ _MODBUS_BACKEND_TYPE_TCP, //1 _MODBUS_TCP_HEADER_LENGTH, //7 _MODBUS_TCP_CHECKSUM_LENGTH, //0 MODBUS_TCP_MAX_ADU_LENGTH, //260 ``` 這裡僅說明各成員之數值,與其Modbus Specification做對應。 ![](https://i.imgur.com/crBP0vb.png) --- ![](https://i.imgur.com/ZVC54gL.png) --- ![](https://i.imgur.com/FO6njoO.png) --- 這段說明書上的文字就很詳細的定義解釋了。 ![](https://i.imgur.com/nS7vpBO.png) --- 最後一上圖,Reference:[實例研討: 從 C++ 學習 C 高級技巧](https://goodspeedlee.blogspot.com/2016/10/c-c.html) 今天就寫到這,感謝您的閱讀。