通常情况下,对于模块或者设备之间通信,往往是以字节流的方式来传输,而模块内部却要将这些字节流按某种数据结构来处理。这就存在,如何将数据结构(结构体)转换(序列化)成字节流,已经如何将字节流转换(反序列化)成数据结构的方法。
这不是很简单么?
tStrcut s;
unsigned char buff[100];
memcpy(buff, &s, sizeof(s)); // serial
memcpy(%s, buff, sizeof(s)); // de-serial
似乎,这也没什么毛病,非要挑毛病,那也只是结构体对齐的一点点问题。对于我们追求卓越的程序猿,还有什么更好的方法呢?
之前,我们学习了X-MACRO,何不用一下?
首先,我们定义一个宏:
#define EXPAND_STAR \
EXPAND_STAR_MEMBER(x, int) \
EXPAND_STAR_MEMBER(y, int) \
EXPAND_STAR_MEMBER(z, int) \
EXPAND_STAR_MEMBER(radius, double)
然后,在用这个宏定义结构体:
typedef struct
{
#define EXPAND_EXPAND_STAR_MEMBER(member, type) type member;
EXPAND_STAR
#undef EXPAND_EXPAND_STAR_MEMBER
} starStruct;
那么这个序列化和反序列化方法可以这样:
void serialize_star(const starStruct *const star, unsigned char *buffer)
{
#define EXPAND_STAR_MEMBER(member, type) \
memcpy(buffer, &(star->member), sizeof(star->member)); \
buffer += sizeof(star->member);
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
void deserialize_star(starStruct *const star, const unsigned char *buffer)
{
#define EXPAND_STAR_MEMBER(member, type) \
memcpy(&(star->member), buffer, sizeof(star->member)); \
buffer += sizeof(star->member);
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
如何打印结构体元素呢?元素中有int
和double
,打印方式是不一样的,于是,我们可以这样做:
#define print_int(val) printf("%d", val)
#define print_double(val) printf("%g", val)
void print_star(const starStruct *const star)
{
/* print_##type will be replaced with print_int or print_double */
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
printf("%s: ", #member); \
print_##type(star->member); \
printf("\n");
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
#include
来改善我是不满足于此的,至少,我对宏定义后面的反斜杠\
看着不顺眼,总是有种强迫症要把它删掉。仔细想想,宏定义的各种奇技淫巧,实际上有部分内容是可以放在头文件(或者被一个#include
)的文件。如以下内容是可以放在一个叫xmacro.def的文件:
// xmacro.def
EXPAND_STAR_MEMBER(x, int)
EXPAND_STAR_MEMBER(y, int)
EXPAND_STAR_MEMBER(z, int)
EXPAND_STAR_MEMBER(radius, double)
#undef EXPAND_STAR_MEMBER
什么?.def
后缀也行?如果你真的有这样的疑问,你肯定没认真看我的《基于C99规范,最全C语言预处理知识总结》,不仅可以命名成xmacro.def,我还可以将它命名成xmacro.fxxk你信不信?
好了,在使用这些宏定义的.c
文件中,这样用:
typedef struct
{
#define EXPAND_EXPAND_STAR_MEMBER(member, type) type member;
#include "xmacro.def"
} starStruct;
其他的EXPAND_STAR
和#undef EXPAND_STAR_MEMBER
都可以替换成#include "xmacro.def"
。
在这个案例中,这个更改似乎没看出什么好处。别急,后面再举一个例子给你感受下include
的威力!
对于序列化的这例子,满意么?No!还有可以优化的地方。
来,从上面的打印元素的函数print_star
入手,这次要发挥两个井号##
的作用。
#define FORMAT_(type) FORMAT_##type
#define FORMAT_int "%d"
#define FORMAT_double "%g"
void print_star(const starStruct *const star)
{
/* FORMAT_(type) will be replaced with FORMAT_int or FORMAT_double */
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
printf("%s: " FORMAT_(type) "\n", #member, star->member);
#include "star.def"
}
是不是很好玩?还有呢,继续搞下去。
__VA_ARGS__
来改善你知道宏定义中的不定参数...
怎么玩么?
#define EXPAND_STAR \
EXPAND_STAR_MEMBER(x, int) \
EXPAND_STAR_MEMBER(y, int) \
EXPAND_STAR_MEMBER(z, int) \
EXPAND_STAR_MEMBER(radius, double)
可以改成:
#define EXPAND_STAR(_, ...) \
_(x, int, __VA_ARGS__) \
_(y, int, __VA_ARGS__) \
_(z, int, __VA_ARGS__) \
_(radius, double, __VA_ARGS__)
这个__VA_ARGS__
就是针对...
的,详细使用方法见《基于C99规范,最全C语言预处理知识总结》。
那么以上的代码可以更改成:
#include
#include
/*
Generic
*/
#define STRUCT_MEMBER(member, type, dummy) type member;
#define SERIALIZE_MEMBER(member, type, obj, buffer) \
memcpy(buffer, &(obj->member), sizeof(obj->member)); \
buffer += sizeof(obj->member);
#define DESERIALIZE_MEMBER(member, type, obj, buffer) \
memcpy(&(obj->member), buffer, sizeof(obj->member)); \
buffer += sizeof(obj->member);
#define FORMAT_(type) FORMAT_##type
#define FORMAT_int "%d"
#define FORMAT_double "%g"
/* FORMAT_(type) will be replaced with FORMAT_int or FORMAT_double */
#define PRINT_MEMBER(member, type, obj) \
printf("%s: " FORMAT_(type) "\n", #member, obj->member);
/*
starStruct
*/
#define EXPAND_STAR(_, ...) \
_(x, int, __VA_ARGS__) \
_(y, int, __VA_ARGS__) \
_(z, int, __VA_ARGS__) \
_(radius, double, __VA_ARGS__)
typedef struct
{
EXPAND_STAR(STRUCT_MEMBER, )
} starStruct;
void serialize_star(const starStruct *const star, unsigned char *buffer)
{
EXPAND_STAR(SERIALIZE_MEMBER, star, buffer)
}
void deserialize_star(starStruct *const star, const unsigned char *buffer)
{
EXPAND_STAR(DESERIALIZE_MEMBER, star, buffer)
}
void print_star(const starStruct *const star)
{
EXPAND_STAR(PRINT_MEMBER, star)
}
像EEPROM、Flash(包含MCU内部的CodeFlahs、DataFlash,以及专门的外设NorFlash、NandFlash)这样的存储空间,有Block、Section等概念。MCU访问其时,需要按照某种约定去访问,也就是说,不能想在任意一个位置写任意长度的数据。例如,如果DataFlash的一次写单位是8个字节的一个block,那么MCU就必须每次写8字节倍数大小的数据内容,而不能随意写内容到一个block中的特定位置。这些就导致在软件设计上诸多不便。
那么,我们是否有方法,将这些内存空间预先规划好,上层软件通过特定接口访问这些分好区域的空间?
遇到这个问题,通常我们惯性地想,将指定内容到指定地址不就可以了?
以一个block为8字节大小的DataFlash举例,其起始地址为0x00008000。
#define DATA1_ADDR 0x00008000
#define DATA2_ADDR 0x00008008
#define DATA3_ADDR 0x00008010
#define DATA4_ADDR 0x00008020
然后再限定每个数据的长度
#define DATA1_LEN 0x00008000
#define DATA2_LEN 0x00008008
#define DATA3_LEN 0x00008010
#define DATA4_LEN 0x00008020
似乎,这也能玩,但是每次改起来不嫌累么?而且也不好维护啊,换一个IC怎么搞?
按照上面的结构体系列化例子的方法,我们尝试使用X-MACRO将Memory结构化。
以下是一些设想:
#define ALL_ENTRIES() \
ENTRY(L_ADDR_DATA1, 8) \
ENTRY(L_ADDR_DATA2, 8) \
ENTRY(L_ADDR_DATA3, 16) \
ENTRY(L_ADDR_DATA4, 16)
然后通过ENTRY定义逻辑地址
#define ENTRY(addr, len) addr
再定义长度
#define ENTRY(addr, len) len
呃……好像不对劲,这个逻辑地址还是要人为定义才行。
发散下思维,我们或许可以将ENTRY(addr, len)
中的addr
定义为enum
,当做是条目ID,而逻辑地址通过其他方式实现(结构体?)。
那,我们将上面的内容改一下:
#define ALL_ENTRIES() \
ENTRY(ID_DATA1, 8) \
ENTRY(ID_DATA2, 8) \
ENTRY(ID_DATA3, 16) \
ENTRY(ID_DATA4, 16)
第一步,定义ID
#undef ENTRY
#define ENTRY(id, len) id,
typedef enum
{
ALL_ENTRIES()
MEM_ID_MAX
}MEM_ID;
第二步,定义逻辑地址(定义结构体)
我们可以,用一个结构体来map其内存逻辑地址
#undef ENTRY
#define ENTRY(id, len) unsigned char mem_##id[len],
struct MemAddrAlign
{
ALL_ENTRIES()
};
第三步,定义逻辑地址(定义逻辑地址数组)
再通过结构体的元素offset来取其元素的位置
#define offsetof(s,m) ((int)((char*)&((s*)0)->m))
#undef ENTRY
#define ENTRY(id, len) offsetof(struct MemAddrAlign,mem_##c),
const unsigned int mem_entry_logic_addr[MEM_ID_MAX]=
{
ALL_ENTRIES()
};
第四步,定义数据长度
#undef ENTRY
#define ENTRY(id, len) len,
const unsigned int mem_entry_size[MEM_ID_MAX]=
{
ALL_ENTRIES()
};
最后,我们就可以通过数据块定义的ID(即enum
)来对mem_entry_logic_addr
和mem_entry_size
的取下标访问来操作Memory了。
等等,这里好像还有个问题,ALL_ENTRIES()
里面定义的长度不是按block 8字节对齐,怎么办?毕竟每个Entry都要求定义成8字节的倍数,似乎并不友好。
部分内容已省略。
请关注公众号“嵌入式软件实战派”获得完整文章和相关源码。
另见:宏的高级用法——X-MACRO