[流畅的 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 吧!