可变长结构体使用小记

        在C++编程过程中,无固定长度的数组可使用vector、list、set等STL的模板来进行管理,虽然不指定长度的话,系统有可能会做一个重新申请空间、拷贝、释放原有空间的操作,但是把编码从繁杂的用户内存管理中释放出来还是挺香的。

        只不过C编程是没有这些方便的容器的(倒是可以自己写),C++不适合用来对外提供接口,而对外提供的C接口,在部分情况下,结构体中某个数组的长度是不固定的,是在结构体赋值之前才能确认,因此就需要用到可变长结构体。下面就是我定义的一个结构体changeStruct:

#include <string.h>
#include <iostream>
using namespace std;

struct changeStruct {
    int32_t testInt1;
    int32_t testInt2;
    uint8_t* testChar[0];
};

        其中testChar[0] 在部分编译器中写成testChar[] 也是可以正常编译运行的,但我建议加上0,便于阅读和维护 (之前看其他大佬的文章,有些编译器不识别testChar[0],需要写成testChar[1],但是我没有遇到过,所以不做具体说明)。

        testChar[0] 是不占用长度的,即利用sizeof(changeStruct) 计算结构体的大小,返回的是8,也就是两个int32_t 类型所占用的长度。这里之所以在结构体中定义两个int32_t 类型的数据,是因为如果只定义一个,会做一个字节对齐,sizeof(changeStruct) 返回的也是8,无法清晰地看出变长数组不占用长度。

        现在要给结构体中的字段进行赋值,首先要通过malloc分配空间,由于上一段我们知道sizeof 计算出来的长度是不包含变长数组的,那么就要根据实际情况加上对应的空间:

changeStruct* CS = (changeStruct*)malloc(sizeof(changeStruct) + 2 * sizeof(uint8_t*));
std::cout << "Size of changeStruct is: " << sizeof(changeStruct) << std::endl;
        
std::string testStr1 = "asdf";
CS->testChar[0] = (uint8_t*)malloc(testStr1.length());
memcpy((char*)CS->testChar[0], testStr1.c_str(), testStr1.length());
std::string testStr2 = "jkl";
CS->testChar[1] = (uint8_t*)malloc(testStr2.length());
memcpy((char*)CS->testChar[1], testStr2.c_str(), testStr2.length());	
std::cout << "testchar 0: " << CS->testChar[0] << ", testchar 1: " << CS->testChar[1] << std::endl;

        上述代码中,变长数组包含2个元素,因此malloc分配的内存就要加上两个指针的长度,这里一定要注意是加的指针长度,写代码时不要漏掉那个*。每个变长数组的指针元素指向的实际的值可能会非常的大,比如要存储图片数据,所以也需要再malloc 分配一段内存出来。

        可变长结构体还有一个好处就是,它所占用的内存空间,和前面的字段的空间是连续的,就是说testChar[0] 是接着testInt2 的内存分配的。同时malloc和free是一一对应的,在上一段代码中,我们总共分配了3个空间,如果只对CS进行free的话就会造成内存泄漏,具体测试代码如下:

uint8_t** char0Addr = &(CS->testChar[0]);
uint8_t** char1Addr = &(CS->testChar[1]);
changeStruct** CSAddr = &CS;
std::cout << "test int1 addr: " << &(CS->testInt1) << ", test int2 addr: " << &(CS->testInt2) << ", testchar 0 addr: " << char0Addr << ", testchar 1 addr: " << char1Addr << std::endl;
std::cout << "testchar 0 restruct: " << *char0Addr << ", testchar 1 restruct: " << *char1Addr << ", CS restruct: " << *CSAddr << std::endl;
    
free(CS);
CS = nullptr;
std::cout << "testchar 0 rerestruct: " << *char0Addr << ", testchar 1 rerestruct: " << *char1Addr << ", CS rerestruct: " << *CSAddr << std::endl;

        上面两段代码的运行结果如下:

        通过结果的第3行,可以看到内存地址的分配是连续的,通过结果的4行,可以看到CS释放之前,3个分配出来的空间都是有数据的,通过结果的第5行,可以看到CS正确释放了,而变长数组元素中指针指向的数据仍未被释放。因此在释放可变长结构体时要分别进行释放,释放之后置空,避免造成野指针,同时也要注意释放之后不要再次使用或释放,会导致崩溃:

CS = nullptr;
CS->testChar[0] = nullptr;
free(CS->testChar[1]);
CS->testChar[1] = bullptr;
free(CS);
CS = nullptr;

        自从C++ 引入了容器和智能指针之后,我就更倾向于让系统去管理内存了,但是一个是可能造成效率的降低,另一个是这种情况也非绝对安全,不过不在本文的讨论范围。对指针的使用一直是我很头秃的地方,本文也只是我的理解,如果哪里有不对的地方,还需要大家交流指正。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值