[流暢的 C]C語言將結構體轉化為字串
[流暢的 C] C語言將結構體轉化為字串
Overview
思路
直接使用 memcpy 之類的是不可以的。所以最好的做法就是定義結構體的時候就實現對字串的轉換。
就像 Python 的
__str__
一樣。 (不好意思,博主雷打不動轉python!信仰上帝Python) 如果不懂 python 也沒有關係,我在下面會說明 C 語言的方法。
假設你有一個 packet:
struct protocol_packet { /* 假設 open source 已經幫你把位元組碼翻譯成 ACSII / HEX 完成*/
unsigned char Mac[16];
unsigned char IPv4Addr[64];
};
然後你你在一個 open source 程式碼中,有個函式,傳遞了接受過來的packet:
int
I_am_open_source_handler(struct protocol_packet recv_packet){
// ...do things...
return 0;
}
packet to string “抽象實現”
這個時候你想把 packet 打印出來看看裡面有什麼。 我們可以這麼做:
免責宣告,因為個人時間有限,沒有對以下純手打抽象出來的程式碼實驗, 但是思路不會錯,所以具體細節還需要看官自己微調一下。
struct protocol_packet { /* 假設 open source 已經幫你把位元組碼翻譯成 ACSII / HEX 完成*/
unsigned char Mac[16];
unsigned char IPv4Addr[64];
int (*packet2str)(*dstStr, struct protocol_packet);
int (*spacket2str)(*dstStr, struct protocol_packet, size_t);
};
int protocol_packet2str(char dstStr[], struct protocol_packet * recv_packet){
/** why char dstStr[]?: 這個和 char *dstStr, 是完全一樣的,這些寫只是為了方面瞭解含義。實際上它不是陣列,關鍵詞:“指標退化”
* 我會假設你知道 *recv_packet 這麼傳遞有助於提升執行效率,如果你對 const 熟悉,我建議“像強迫症”一樣給它加上 const,這樣能夠說明本函式不會改變 recv_packet 的內容。
* 你無法直接拷貝整個 struct, 所以只能針對它內部的成員一個一個拷貝,
* 因為是用來輸出的,所以這個時候你可以直接新增一些欄位的含義說明在 dstStr 中
*/
unsigned char mapping_struct_Mac[16];
unsigned char mapping_struct_IPv4Addr[64];
memcpy(mapping_struct_Mac, recv_packet->Mac, 16); /* 假設 open source 已經將 MAC 解析成了 FF0073E4FFFF 的形式*/
memcpy(mapping_struct_IPv4Addr, recv_packet->IPv4Addr, 64);
/**
* 現在你可以使用 snprintf() 定製你的字串輸出了
* 注意, MAC 不能使用 '%s' 的方式, 你得 for 迴圈 使用 %x(%X), 再注意,MAC 不是 16 byte 長度!
* 為了避免誤導,因為我主要的時間都在寫 Python,所以C語言沒有測試會有錯誤,
* 因此下面不給例項,要你自己實現 snprintf() 的具體內容,
* 這裡是思路:
* 比如針對 MAC:
* 0123456
* snprint(dstStr, "MAC: 0x", 7);
* snprint(&dstStr[7], "%X", mapping_struct_MAC[0]) /* for 迴圈 12 次 */
*/
// ...do the final job....
return 0;
}
int sprotocol_packet2str(char dstStr[], struct protocol_packet recv_packet size_t length_of_dstStr){
/** 你可以實現一個 safe 版本,比起上面,只是多了一個 dstStr 的長度檢查; */
return 0;
}
如何使用
這裡有一處必須要注意的地方:
因為我們上面把 struct 裡面的內容改了,增加了一個轉化為 string 的函式指標(還有一個 option 的 safe 方法), 但是如果不給這個指標賦值,它是 NULL 的,不能亂用。 所以,使用的時候,找到 code 裡面初始化 recv_packet 的地方,我假設原本 code 是這樣的:
int
main(...你懂的...){
struct protocol_packet recv_packet;
// make me as daemon, come on!
while(1) { /* loop until universe collapses */
// 收到 frame
拆包func(&recv_packet, ...other if needed...);
I_am_open_source_handler(&recv_packet);
// ..other if needed...
}
return 0;
}
那麼這個位置我們要輕輕一改:
int
main(...你懂的...){
struct protocol_packet recv_packet;
// make me as daemon, come on!
while(1) { /* loop until universe collapses */
// 收到 frame
拆包func(&recv_packet, ...other if needed...);
recv_packet.packet2str = protocol_packet2str; // LOOK AT ME !!!!!!!!
I_am_open_source_handler(&recv_packet);
// ..other if needed...
}
return 0;
}
終於到了最後:
這樣你就可以在任何巢狀在 I_am_open_source_handler()
裡面很深層次的函式裡面這麼使用了:
int
I_am_open_source_handler(struct protocol_packet recv_packet){
// ...do things...
switch([...]){
case [...]:
[...]
case [...]:
i_am_a_func_in_the_HANDLER(&recv_packet);
[...]
break;
}
return 0;
}
int
i_am_a_func_in_the_HANDLER(struct protocol_packet *recv_packet_p){
// 我想要在這裡知道 recv_packet 內部具體有哪些值,
// OK, that's do it:
char my_add_debug_str[2048] = {'\0'}; /* 長一點總不會壞事吧 */
recv_packet_p->packet2str(my_add_debug_str, recv_packet_p); /* 注意,這裡 recv_packet_p 是指標型別哦 */
printf("%s", my_add_debug_str); /* 看,這樣就可以愉快地輸出了 */
/* 當我知道了 protocol 裡面有哪些內容之後,
* 我可能會在下面的 source code 前面的現在這裡,
* 新增一些自己的程式碼,做一些檢查,定製化等等。
*/
// ..do magic things...
// BUT, I don't care this part code!
}
Summary
當然,你可以定義一個全域性函式,不修改 struct protocol_packet
裡面的內容,全域性函式就跟 protocol_packet2str()
裡面一樣寫就可以了。然後需要的地方,直接呼叫這個全域性函式。
但是這樣寫還要知道這個函式不是嗎?
在產品程式碼裡面,這個函式一般都不會出現在像上面的 i_am_a_func_in_the_HANDLER()
這樣的具體行為的程式碼裡面,但是,如果定義了這樣一個自動轉化的指標,那麼每個結構體初始化的位置: struct protocol_packet recv_packet;
你都可以給它繫結函式:
recv_packet.packet2str = protocol_packet2str; // LOOK AT ME !!!!!!!!
這個繫結語句可以緊跟在初始化位置後面,不用在上面的 while 迴圈之類的內部繫結即可。上面是為了方面理解。
這句程式碼可以留著,因為給結構體的一個成員繫結一個地址並不會消耗什麼資源,並且它不會有任何輸出;當另外一個人,完全沒有接觸過這個程式碼,或者說,你自己幾個月之後又要跟蹤 packet 的狀態… 那麼當你看到這樣一個結構體:
struct protocol_packet { /* 假設 open source 已經幫你把位元組碼翻譯成 ACSII / HEX 完成*/
unsigned char Mac[16];
unsigned char IPv4Addr[64];
int (*packet2str)(*dstStr, struct protocol_packet);
int (*spacket2str)(*dstStr, struct protocol_packet, size_t);
};
你就知道可以這麼呼叫: recv_packet_p->packet2str(debug_str, recv_packet_p);
於是你就可以很自然地寫出這三行程式碼:
char debug_str[2048] = {'\0'};
recv_packet_p->packet2str(debug_str, recv_packet_p);
printf("%s", debug_str);
從此觀察結構體內部狀態就變成了一個很輕鬆地事情了。
Reference
實際上這裡沒有啥 reference, 但是如果你覺的上面的內容有用,有些內容不理解?,想要多瞭解一下? 那麼這裡有一些是上面內容使用到的知識點:
- 指標退化
- C 語言的基於物件程式設計(程式設計正規化)
- 一些一時說不上來的細節
快點學 python 吧!