关于C程序结构体分配内存的释放问题

众所周知,结构体中的属性变量也可以是结构体,于是,可以通过以下方式定义:

struct MyStrtu{
    char*a;
};

struct Global
{
    int *a;
    struct MyStrtu *stringOperaterP; // 字符串处理
};
typedef struct Global Global;

这时候,我们可以通过以下方式生成结构体对象并分配内存:

Global* obj = (Global *)malloc(sizeof(Global));

obj结构体对象分配内存后,实际上,obj的属性变量stringOperaterP也就分配了对应指针的内存。
那么问题来了:
1、我们在后续使用stringOperaterP指针时,首先需要分配内存,即 obj->stringOperaterP=(MyStrtu*)malloc(sizeof(MyStrtu)),这样做,那么原本obj被malloc时,分配给stringOperaterP指针的空间不是就变成了没有指针指向的“野空间”了吗?
2、如果为stringOperaterP进行内存分配,即malloc,那么free掉obj前,是不是要先free掉stringOperaterP指针的空间呢?

这两个问题困扰我许久,望指教!

  1. stringOperator只是指针,分配obj时其初始值没有意义
  2. free要和malloc匹配,free顺序要跟malloc顺序相反

1.指针是指针,值是值,两者不是连续存储的
2.是的,不然可能造成内存泄漏

经过我两天的研究,现在可以回答你的问题了,要回答你的问题得知道三个东西,一个是指针到底是怎么分配空间的,二个malloc到底是怎么分配的空间,还有一个就是结构体的大小怎么来的我做了如下函数供你参考:

#include<stdio.h>
#include<stdlib.h>

struct MyStrtu{
    char*a;
};
struct Global
{
    int *a;
    struct MyStrtu *stringOperaterP; // 字符串处理
};
typedef struct Global Global;

int main()
{
    Global* obj;
    printf("\n一个 int* 的大小为:%d\n\n", sizeof(int*));
    printf("obj分配空间前:\n");
    printf("obj: %p\n\n", obj);
    printf("&obj: %p\n\n", &obj);
    obj = (Global *)malloc(sizeof(Global));
    printf("obj分配空间后:\n");
    printf("obj: %p\n\n", obj);
    printf("&obj: %p\n\n", &obj);

    printf("string分配空间前:\n");
    printf("string: %p\n\n", obj->stringOperaterP);
    printf("&string: %p\n\n", &obj->stringOperaterP);
    obj->stringOperaterP = (MyStrtu*)malloc(sizeof(MyStrtu));
    printf("string分配空间后:\n");
    printf("string: %p\n\n", obj->stringOperaterP);
    printf("&string: %p\n\n", &obj->stringOperaterP);
    printf("\n一个 obj 的大小为:%d\n\n", sizeof(*obj));
    return 0;
}

结果如下:

img

首先,指针就是一个变量,他需要电脑给他分配一定空间来存放地址,所以,现在在我的六十四位电脑里,一个指针的大小就必须要足够存放我这六十四位数据,所以一个指针的大小就是八个字节,而指针的存放方式如下,

img

这个就好比你现在要告诉你的好朋友你的家在哪里,然后你的朋友给你找了一张纸来写下你家的地址,然后他就可以用这张纸上的地址来找到你家了,转化到指针就可以这么理解这个0x1234就是你告诉电脑你存放数据的地方,而0x4000就是电脑给你找的那张纸来记录下你那个变量的地址,当然,电脑给你找的这张纸可不是干净的哦,需要你的地址覆盖掉之前在这张纸上的信息,因为这张纸上可能会放着各种各样的你不需要的垃圾,所以指针在使用前必须进行赋值操作,不然电脑不能通过一堆垃圾来找到你家,而这个垃圾呢就是你看到 obj 在分配地址前里面存的0x0031,而在分配了空间之后电脑将地址存入 obj 就变成了你看到的0x191420,而这张“纸”呢?就是你看到的0x61FE08这个是在你分配地址前后没有变化的;

接下来我们说说malloc,它是要分配你规定的空间之后,将首地址返回一个指针然后强制转化成你规定的类型,这么一个函数,这么说好像有点抽象,举个例子呢就是,你现在要一个一百二十平的房子,并且要三室一厅的一百二十平,这个时候你告诉电脑,然后电脑就会先找到一片一百二十平的空间,然后再强制转化成,你要的三室一厅,最后再把房间的地址放到你的指针里,所以在你分配地址之前,你的 obj 指针就是一张纸,你找不到任何信息,而当你分配空间后,你就可以知道这个 obj 下还有一个 string 指针元素,接下来我们再给这个 string分配空间;

最后一个是我引申出来的问题,为什么 obj 的大小是16,这里希望你知道结构体分配空间遵守两个原则,一个就是每个元素分别按自己的字节数对齐,整个结构体按结构体里最长元素的字节对齐,我给你画个图,现在有一个结构体占这么多空间:

img

然后我们声明结构体元素首先申请一个char,他就占一个字节,这样存放:

img

再来一个int:

img

这么放的原因希望你可以知道这个字节对齐是个什么,再申请一个char就要放到这:

img

这么一看,是不是就有些空间被浪费了呢,是,但是浪费了这些空间同时也带来一些好处,你看,假设我们存了两个 int ,那你希望你这个指针每次向后跳一个字节呢还是四个,肯定是四个,因为如果向后跳一个字节当作我们放到结构体的下一个元素显然是不正确的,所以四字节对齐就有这个好处,我们现在拿到了这个结构体,我希望找到第二个int元素,我们就从首地址向后四个四个字节跳,直到找到我们需要的那个int,这就是结构体分配空间的第一个原则,第二个就是要按最长字节对齐整个结构体,这个我觉得就是第一个原则的广义话,你想想,一个结构体存着char、int、double,三个元素,这个一共要占16个字节,前面说到int四字节对齐是为了寻找int方便,那按最长字节元素对齐是不是就是为了寻找double元素方便呢,由此延伸出一个说法就是,结构体最优排放元素顺序,显然,上面提到的char、int、char排列方法不如,char、char、int节省空间,

img

放到你的代码里我做一个小改动,

img

结构体大小变成了24, 你可以想想为什么,提示一下在字符数组和指针中间空了六个字节;

掌握了上面的知识之后,现在来看看你的问题
你第一个问题说malloc给string的指针变成了没有指向的野空间空间了,实际上不是这样的第一张图你可以看到string空间分配前后他的地址并没有改变,对于obj而言,string指针里面存的什么并不重要,你给obj分配空间是不是只需要里面能放下你这个string指针就够了呢?然后,你再给string指针分配空间,然后,电脑吧新空间的地址放到string里面,然后string通过这个地址找到那片空间,是个这样的流程,在结尾你看到,obj的大小还是16,也验证了上面的观点(obj里两个指针)

第二个问题你说的没错指针指向其他空间前,原本的指向如果没有释放,是会造成内存泄露,所以人们常说malloc和free要成对出现,new和delete要成对出现,所要解决的问题正是内存泄漏问题,内存泄漏的问题不在于你释放的这一下,他是一个累计的过程,当我们的程序比较小的时候,是,尽管我们不释放掉string直接释放obj也没有关系,造成的内存泄漏当然也无伤大雅,但是当你的程序变大,内存不断地泄露,慢慢的就会影响到你整个程序的运行效率了,最终导致程序运行速度变慢,甚至崩溃,而释放malloc分配的空间的办法就是free,free的方法就是从里到外一层层的释放,你上面说外面的释放掉了之后在释放里面的有没有办法,你想想,你把外面的空间释放掉了之后,那原地方放的数据虽然没有消失,但是他们都变成了垃圾,电脑就不能通过这些信息解析出来你的string的地址了,就像上面我说的结构体需要按位对齐这里一样,这时候指针是一次向后跳四个字节,但是你释放了之后可能电脑就不知道向后跳几个才是正确的了,所以释放的时候就要注意,从内到外一层层的释放,不要犯这种错误