1.常见的动态内存的错误
我们在学习动态内存的时候,常出现的一些错误我们来看一下。
1.对NULL指针的解引用操作
例如我们在使用malloc或者calloc开辟动态空间的时候,有时候没有判断是否开辟成功而直接对齐的返回指针进行解引用,此时如果开辟失败,返回值为空指针,此时就会出错。
演示如下:
上述代码中我们开辟了10个整型大小的空间,并将calloc的返回值强转为int *类型的指针赋给pt,并解引用pt找到开辟的空间并赋值1,后释放空间。我们知道此处的calloc的返回值肯能为NULL,如果我们直解引用的话,编译器就会报错,free是没啥问题的,因为我们在之前学习到我们给free传NULL的话,free就不会有任何操作。
2. 对动态开辟空间的越界访问
在之前我们学习到开辟数组又是会越界访问,那么在动态开辟空间中也会有这样的现象吗?答案肯定的。演示如下:
上述代码中我们创建了10个整型空间,并将它的返回值强转为int*类型的,我们用assert对pt进行断言,当pt为NULL时就会报错。我们运用for循环对申请的空间进行访问赋值。当我们的i为10时就会出现越界访问的问题。这是因为我们的i等于10时,就已经访问到了11个整型了。然而我们申请的空间却只有10个整型,所以会出现越界访问的问题。这种行为是十分危险的,所以我们在写代码时要小心。
3. 对非动态开辟内存使用free释放
我们在之前了解到free是专门用来释放动态空间的,那我们能用其来释放非动态空间吗?答案是不能的。演示如下:
我们看这个代码就是一个典型的例子,那么运行结果是什么呢?
我们看到编译器报错了。这就说明了free不能用来释放非动态空间。
4.使用free释放一块动态开辟内存的一部分
我们在申请空间的时候,有时候会对指向该空间的指针进行加减,此时再对该指针进行free就会造成释放一块动态开辟内存的一部分。那么此时会出现什么问题呢?演示如下:
这个时候也会报错。free函数的参数一定是指向动态内存起始位置的地址。你也可以想想为别人借你100,你只还他20,那他肯定就会不愿意。
5.对同一块动态内存多次释放
那我们是否可以对一个空间进行多次释放呢?我们来试一下:
很显然也不可以,这个我们也可以想成我们借别人100,结果让我还200我肯定不愿意呀。
6.动态开辟内存忘记释放(内存泄漏)
我们在申请动态内存空间后却忘记释放,有时候就会导致内存泄漏,有人就会问程序结束的时候,内存不也会回收吗?那如果是死循环呢?那么程序就不会结束,动态内存也就不会回收。所以我们要注意申请动态空间也需要释放空间。
2. 柔性数组
1.什么是柔性数组
也许你从来没有听说过柔性数组(flexiblearray)这个概念,但是它确实是存在的。
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
有些编译器会报错,就需要写成:
2.柔性数组的特点
• 结构中的柔性数组成员前面必须至少一个其他成员。
• sizeof返回的这种结构大小不包括柔性数组的内存。
• 包含柔性数组成员的结用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
这里sizeof计算结构体大小时就没有算入柔性数组的大小。
我们可以在结构体中定义一个指针,然后申请一份空间,在把这个申请的空间给这个指针让它来维护这份空间。
3.柔性数组的优势
好处1:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
好处2:这样有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)。