深入浅出变长结构体

简介: 本文是对深入浅出变长结构体的详解。

1、 问题的引出

项目中用到数据包的处理,但包的大小是不固定的,其长度由包头的2字节决定。比如如下的包头:88 0f 0a ob cd ef 23 00 。长度由头2个字节880f决定,考虑字节序,转为0f88,转为10进制3976个字节的包长度。

这个时候存储包的时候,一方面可以考虑设定包的大小固定:如4K=4*1024=4096个字节,因为最大包长不可能超过4k,但该方法的有缺陷,存在一种极端就是包最小仅含包头不含数据域,此时包为8个字节,浪费了4096-8 =4088个字节的存储空间。另一方面考虑有没有一种方法能根据长度进行存储,或者说初始不分配长度,计算出了长度后再分配存储呢。而实际项目中正是通过包头计算出了包的整体大小的。

这就引出了变长结构体的概念。

2、 什么叫变长结构体?

如下所示:

struct Var_Len_Struct
{
     int nsize;
     char buffer[0];
};

那结构体是怎么实现可变长的呢?如上所示,请注意看结构体中的最后一个元素,一个没有元素的数组。我们可以通过动态开辟一个比结构体大的空间,然后让buffer去指向那些额外的空间,这样就可以实现可变长的结构体了。更为巧妙的是,我们甚至可以用nsize存储字符串buffer的长度。

并且,上述的结构体可以扩展,比如笔者项目中遇到的存储数据包,前面可能类似包头的部分(存储类型、长度等信息),而后面buffer则存储数据部分。

同时,需要引起注意的:ISO/IEC 9899-1999里面,这么写是非法的,这个仅仅是GNU C的扩展,gcc可以允许这一语法现象的存在。但最新的C/C++不知道是否可以,我没有测试过。C99允许。

 

3、变长结构体的好处体现在哪?

可能有的同学会问到,1引出部分如果说定义定长数组浪费空间,定义一个指针不也能指向变长的数据域部分吗?

是的,是可以实现的。那么我们就对比下有什么不同。

结构体1:s_one,用指针指向数据域部分;

结构体2:s_two, 用[0]的数组;

结构体3:s_three, 因为有的编译器不支持[0],我们用[1]来表示;多了些存储。

 

#include <stdafx.h>
#include <iostream>
using namespace std;
 
const int BUF_SIZE = 100;
 
struct s_one
{
ints_one_cnt;
char*s_one_buf;
};
 
struct s_two
{
ints_two_cnt;
chars_two_buf[0];
};
 
struct s_three
{
ints_three_cnt;
chars_three_buf[1];
};
 
int main()
{
//赋值用
constchar* tmp_buf = "abcdefghijklmnopqrstuvwxyz";
intntmp_buf_size = strlen(tmp_buf);
 
//<1>注意s_one 与s_two的大小的不同
cout<< "sizeof(s_one) = " << sizeof(s_one) << endl; //8
cout<< "sizeof(s_two) = " << sizeof(s_two) << endl; //4
cout<< "sizeof(s_three) = " << sizeof(s_three) << endl;//5-->8结构体对齐
cout<< endl;
 
//为buf分配100个字节大小的空间
intntotal_stwo_len = sizeof(s_two) + (1 + ntmp_buf_size) * sizeof(char);
intntotal_sthree_len = sizeof(s_three) + ntmp_buf_size * sizeof(char);
 
//给s_one buf赋值
s_one*p_sone = (s_one*)malloc(sizeof(s_one));
memset(p_sone,0, sizeof(s_one));
p_sone->s_one_buf= (char*)malloc(1 + ntmp_buf_size);
memset(p_sone->s_one_buf,0, 1 + ntmp_buf_size);
memcpy(p_sone->s_one_buf,tmp_buf, ntmp_buf_size);
 
//给s_two buf赋值
s_two*p_stwo = (s_two*)malloc(ntotal_stwo_len);
memset(p_stwo,0, ntotal_stwo_len);
memcpy((char*)(p_stwo->s_two_buf),tmp_buf, ntmp_buf_size);  //不用加偏移量,直接拷贝!
 
//给s_three_buf赋值
s_three*p_sthree = (s_three*)malloc(ntotal_sthree_len);
memset(p_sthree,0, ntotal_sthree_len);
memcpy((char*)(p_sthree->s_three_buf),tmp_buf, ntmp_buf_size);
 
cout<< "p_sone->s_one_buf = " << p_sone->s_one_buf<< endl;
cout<< "p_stwo->s_two_buf = " << p_stwo->s_two_buf<< endl;
cout<< "p_sthree->s_three_buf = " <<p_sthree->s_three_buf << endl; //不用加偏移量,直接拷贝!
cout<< endl;
 
//<2>注意s_one 与s_two释放的不同!
if(NULL != p_sone->s_one_buf)
{
        free(p_sone->s_one_buf);
        p_sone->s_one_buf= NULL;
 
        if(NULL != p_sone)
        {
               free(p_sone);
               p_sone= NULL;
        }
        cout<< "free(p_sone) successed!" << endl;
}
 
if(NULL != p_stwo)
{
        free(p_stwo);
        p_stwo= NULL;
 
        cout<< "free(p_stwo) successed!" << endl;
}
 
if(NULL != p_sthree)
{
        free(p_sthree);
        p_sthree= NULL;
 
        cout<< "free(p_sthree) successed!" << endl;
}
 
return0;
}

 

笔者vc6.0的编译器会有如下的警告:

image.png

 

运行结果如下:

image.png

对比结果,我们能发现:

<1> 存储大小方面:s_two的存储较s_one、s_three都要少,[0]的好处,即用指针的方式需要多开辟存储空间的。

<2> 数据连续存储方面:s_one明显数据域是单独开辟的空间,与前的nsize不在连续的存储区域,而s_two,s_three则在连续的存储空间下。

<3>释放内存方面:显然s_one的指针的方式,需要先释放数据域部分,才能释放指向结构体的指针变量;而s_two,s_three可以直接释放。

总结如下:

结构体最后使用0或1的长度数组的原因,主要是为了方便的管理内存缓冲区,如果你直接使用指针而不使用数组,那么,你在分配内存缓冲区时,就必须分配结构体一次,然后再分配结构体内的指针一次,(而此时分配的内存已经与结构体的内存不连续了,所以要分别管理即申请和释放)。

而如果使用数组,那么只需要一次就可以全部分配出来,反过来,释放时也是一样,使用数组,一次释放,使用指针,得先释放结构体内的指针,再释放结构体。还不能颠倒次序。

其实变长结构体就是分配一段连续的的内存,减少内存的碎片化,简化内存的管理。

 

4、变长结构体的应用

       <1>Socket通信数据包的传输;

       <2>解析数据包,如笔者遇到的问题。

       <3>其他可以节省空间,连续存储的地方等。

 

 

未尽事宜,后续补上……

     

 

作者:铭毅天下

转载请标明出处,原文地址:http://blog.csdn.net/laoyang360/article/details/11908731

相关文章
|
XML 开发框架 前端开发
WPF学习之基础知识篇
WPF(Windows Presentation Foundation)具有一个复杂且强大的架构,旨在提供丰富的用户界面、图形、动画和多媒体功能。
633 9
|
Windows
msi文件解包
msi文件解包
2547 1
msi文件解包
|
存储 Java Android开发
Android插件化动态加载apk
支付宝作为一个宿主apk提前将要集成的apk作为一个插件(plugin)下载到本地,然后当使用该plugin(apk)的时候再去加载对应plugin(apk)的资源文件以及对应的native页面。就是不去安装plugin(apk)就可以直接运行该plugin(apk)中的页面。
1392 0
|
设计模式 供应链
一文教会你如何写复杂业务代码
了解我的人都知道,我一直在致力于应用架构和代码复杂度的治理。 这两天在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。针对该命题,我进行了比较细致的思考和研究。
38964 3
国家互联网信息办公室关于发布第十批深度合成服务算法备案信息的公告
2025年3月12日,国家网信办公布第十批深度合成算法备案信息,共395款算法通过公示。根据《互联网信息服务深度合成管理规定》,境内深度合成服务提供者和技术支持者需履行备案手续。具体信息可在中国互联网信息服务算法备案系统查询,疑议请发邮件至指定邮箱。附件含完整备案清单。
|
存储 缓存 JavaScript
cookie和localStorage的区别特点
cookie和localStorage的区别特点
814 1
|
人工智能
【经验分享】如何快速转化笔记格式为标准的MarkDown格式并进行博客发布,提高生产力?
本文介绍如何将笔记转换为Markdown格式以快速发布博客。通过使用特定的Prompt和AI工具Claude 3 Sonnet,可以将Notepad++笔记转为适合CSDN博客的Markdown格式。转换要求包括:正确标记代码段、调整缩进和格式、使用Markdown标题、列表、链接和图片语法。Claude 3 Sonnet能有效处理格式转换,将转换后的Markdown内容复制到编辑器,即可便捷发布博客。
989 2
【经验分享】如何快速转化笔记格式为标准的MarkDown格式并进行博客发布,提高生产力?
|
语音技术 C++
基于Qt的简易语音识别与合成系统设计与实现
基于Qt的简易语音识别与合成系统设计与实现
1106 0
|
存储 安全 前端开发
玄铁RISC-V处理器入门与实战-无剑平台-无剑100开源SoC平台
玄铁RISC-V处理器入门与实战-无剑平台
1464 1