1. 出现本文的原因
2. 介绍 这种技巧
3. 修改书中的错误
1. 出现本文的原因
在《深度探究c++对象模型》看到这样一段代码:
1 struct mumble {
2 /* stuff */
3 char pc[ 1 ];
4 };
5 // grab a string from file or standard input
6 // allocate memory both for struct & string
7 struct mumble *pmumb1 = ( struct mumble* ) malloc(sizeof(struct mumble)+strlen(string) + 1);
8 strcpy( &pmumb1.pc, string );
在那本书中并不是为了介绍 "把单一元素的数组放在一个struct的尾端问题" 的这个trick的。但在看项目中服务器发包给客户端的时候,也用到了这种格式,为什么要这么写,分析记录一下。
2. 介绍 这种技巧
简化项目中例子:
定义一个可变包结构&&如何往可变包结构中填充数据。
#pragma pack(1) typedef unsigned char BYTE; // 1. 定义一个 送花排行信息 包结构 。结构体的最后是 只含一个元素的一维数组。 struct FlowerRank { // 2. 定义 排行信息单元,玩家名字和被送花数目 也就是 可变单元里 的元素的结构。 struct RankUnit { char szName[32]; int iNum; RankUnit() { memset(szName, 0, 32 * sizeof(char)); iNum = 0; } }; BYTE packetType; // 指明此消息的类型 方便接收方解析消息。 RankUnit rankData[1]; // 排行信息单元 从这里开始存。注:此变量也可成 char rankData[1]; }; // int main() {
// 获取下结构体的大小 int a = sizeof(FlowerRank); // 37 size //注:如果 char rankData[1] ,那么 sizeof(FlowerRank) = 2 size; a = sizeof(FlowerRank::RankUnit); // 36 size
// 3. 根据需要塞入的 排行信息单元的 数目多少,申请对应的内存空间。 const int iCounts = 10; // 10个单元 // 占了一段内存,在此上对应结构体成员。 pData 大小 361 char pData[sizeof(FlowerRank::RankUnit) * (iCounts -1) + sizeof(FlowerRank)] = { 0 }; // 注:如果 char rankData[1] ,那么 // char pData[sizeof(FlowerRank::RankUnit) * iCounts + sizeof(FlowerRank) - 1] = { 0 }; a = sizeof(pData); // 361 size
// 4. 在申请的空间上 定义变量类型 如何读取内存 FlowerRank* pPacket = new (pData) FlowerRank; FlowerRank::RankUnit* pRankList = new (pPacket->rankData) FlowerRank::RankUnit[iCounts]; // 5. 塞入实际的信息。为了方便书写10个都填入一样的数据了 for (int i = 0; i < iCounts; ++i) { strncpy_s(pRankList[i].szName, "Goddess", 32); pRankList[i].iNum = 10; } // 6. 发送包 pPacket
return 0; }
可以看到定义可变包结构时候,结构中没有可变包的大小,而是只要在结构里最后加一个元素的字节数组就可以。(也可以加一个元素的 信息单元结构的数组,这样方便理解,但是结构体大小会大点)。
这个技巧利用了两点:
1) struct在内存中的布局。
2) 指针之间的相互转换,内存地址可以按照不同类型的数据来解释。
这里有一个问题:把
RankUnit rankData[1] ----》RankUnit* rankData; 会怎么样?
char rankData[1] ----》char* rankData; 会怎么样?
这样整个结构体的数据是不连续的的。改变成指针后指向了另外一块内存,而不是和结构体的其他数据成员连续存放了。
3. 修改书中的错误
struct mumble { /* stuff */ char pc[ 1 ];
};
// 0. 注意下面的 string不是类型,是实际字符串。
// 1. 去掉 + 1, 因为尽管strlen是不包括 的,但因为pc中已经有一个字节了,所以这里不需要再加1了。当然加1也不会有问题,只是没有必要而已。
struct mumble *pmumb1 = ( struct mumble* ) malloc(sizeof(struct mumble)+strlen(string) + 1);
struct mumble *pmumb1 = ( struct mumble* ) malloc(sizeof(struct mumble)+strlen(string));
// 2. pmumbl是指针,应该用->;而且pc是指针了,也不能再取地址了。
strcpy( &pmumb1.pc, string );
strcpy_s(pmumb1->pc,strlen(string) + 1, string);