900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > c语言将结构体转换为字符串 [流畅的 C]C语言将结构体转化为字符串

c语言将结构体转换为字符串 [流畅的 C]C语言将结构体转化为字符串

时间:2021-09-17 14:59:31

相关推荐

c语言将结构体转换为字符串 [流畅的 C]C语言将结构体转化为字符串

[流畅的 C] C语言将结构体转化为字符串

本文并非标题的具体实现。而是提供一种编程方式,习惯,一种探讨。

本文有一点点门槛,有 socket,开源协议栈学习/开发经验者阅读更佳。

Overview

[*流畅的 C*] C语言将结构体转化为字符串

思路

struct (packet) to string “抽象实现”

如何使用

全文亮点

Reference

思路

直接使用 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;

}

struct (packet) to string “抽象实现”

这个时候你想把 packet 打印出来看看里面有什么。

我们可以这么做:

免责声明,因为个人时间有限,没有对以下纯手打抽象出来的代码实验,

但是思路不会错,所以具体细节还需要看官自己微调一下。

struct protocol_packet { /* 假设 open source 已经帮你把字节码翻译成 ACSII / HEX 完成*/

unsigned char Mac[16];

unsigned char IPv4Addr[64];

int (*packet2str)(char*, struct protocol_packet*);

int (*spacket2str)(char*, 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 成为你想要的 string,

* 因为有些成员可能是 HEX 之类的,输出时会有问题,

* 所以只能针对它内部的成员一个一个拷贝;

* 因为是用来输出的,所以这个时候你可以直接添加一些字段的含义说明在 dstStr 中

*/

unsigned char mapping_struct_Mac[16];

unsigned char mapping_struct_IPv4Addr[64];

/* 假设 open source 已经将 MAC 解析成了 FF0073E4FFFF 的形式*/

memcpy(mapping_struct_Mac, recv_packet->Mac, 16);

memcpy(mapping_struct_IPv4Addr, recv_packet->IPv4Addr, 64);

/** 现在你可以使用 snprintf() 定制你的字符串输出了

* 注意, MAC 不能使用 '%s' 的方式, 你得 for 循环 使用 %x(%X), 再注意,MAC 不是 16 byte 长度!

* 为了避免误导,因为我主要的时间都在写 Python,所以C语言没有测试可能会有错误,

* 因此下面不给实例,要你自己实现 snprintf() 的具体内容,这里是思路:

* 比如针对 MAC: 01234 */

snprint(dstStr, 5, "MAC: "); /* 有没有人能教我一些 C 语言里面计算偏移的技巧??? */

snprint(&dstStr[5], 18, "%02X:%02X:%02X:%02X:%02X:%02X",

mapping_struct_MAC[0], mapping_struct_MAC[1], mapping_struct_MAC[2],

mapping_struct_MAC[3], mapping_struct_MAC[4], mapping_struct_MAC[5]);

// ...do the final job by you....

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 -网络字节码-> theFrame

拆包func(&recv_packet, &theFrame, /*..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 -网络字节码-> theFrame

拆包func(&recv_packet, &theFrame, /*..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!

}

全文亮点

当然,你可以定义一个全局函数,不修改 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 内部脑海里就出现黑人问号…

那么当你看到这样一个结构体:

struct protocol_packet { /* 假设 open source 已经帮你把字节码翻译成 ACSII / HEX 完成*/

unsigned char i_am_not_named_mac_addr[16];

unsigned char i_have_a_strange_name[64];

int (*packet2str)(char*, struct protocol_packet*);

int (*spacket2str)(char*, struct protocol_packet*, size_t);

};

你就知道可以这么调用:

recv_packet_p->packet2str(debug_str, recv_packet_p);

来的得到整个结构体内部变量的值是怎么样的了,然后很轻松地把它打印显示出来。

非指针是这么调用: recv_packet.packet2str(debug_str, &recv_packet) ;

于是你就可以很自然地写出这三行代码:

char debug_str[2048] = {'\0'};

recv_packet_p->packet2str(debug_str, recv_packet_p);

printf("%s", debug_str);

从此观察结构体内部状态就变成了一个很轻松地事情了。

一般我们可能只是要个输出,那么完全可以定义一个

void (*packet_format_print)(struct protocol_packet*); 在结构体内部,

只不过这个方法可能不足够安全,因为一些系统知识有关的原因,在这个函数内部的实现输出根据不同的平台需要做一个适配的调整。比如输出到文件/syslog?输出到默认终端?输出到 console? 是指这个意思。

在初始化结构体的时候绑定函数,就是基于对象的做法,类似 C++ 有个构造函数。

struct 可以当作一个 default public 的类来使用,所以只要自己定义好了构造函数,在 C 里面也是可以很方便的直接初始化它,而不用上述显式地去绑定函数。

但是这部分说明,对于这个字符串化结构体这个主题,相关性不大。所以这里只是一个提及。

Reference

实际上这里没有啥 reference, 但是如果你觉的上面的内容有用,有些内容不理解?,想要多了解一下?

那么这里有一些是上面内容使用到的知识点:

指针退化;

C 语言的基于对象编程(编程范式)-> 使用函数指针实现对象“方法”;

ACSII 码可以 %s,查看,HEX 码万万不能误用 %s 来输出;

daemon 进程一般程序内部使用 printf 不会输出到终端;

一些一时说不上来的细节。

快点学 python 吧!

欢迎朋友指出上面文章内容的错误之类。

或者有更好的建议。

本人水平有限,本文仅供参考。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。