1. 程式人生 > >[流暢的 C]C語言將結構體轉化為字串

[流暢的 C]C語言將結構體轉化為字串

[流暢的 C] C語言將結構體轉化為字串

Overview

N/A

思路

直接使用 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, 但是如果你覺的上面的內容有用,有些內容不理解?,想要多瞭解一下? 那麼這裡有一些是上面內容使用到的知識點:

  1. 指標退化
  2. C 語言的基於物件程式設計(程式設計正規化)
  3. 一些一時說不上來的細節

快點學 python 吧!