目录
为什么存在动态内存管理
1.可以申请大块内存,进行较为大型的应用
2.可以在程序运行期间进行申请,可以更灵活的使用内存空间。
(申请空间用malloc()函数,必须是程序运行起来才能调用,编译的过程早就过了)
如何进行空间管理?
通过malloc函数申请空间和使用,用free函数释放空间
常见细节:
1.整体申请,整体释放。
2.如果申请内存不归还,会造成内存泄漏。
内存泄漏:一种严重的问题,会造成可用内存越来越少。
进程退出了,内存泄漏问题就不在了。
程序分为:一运行立马结束的程序和常驻程序(死循环的,不会结束的程序),常驻程序容易造成内存泄漏。
3.申请有大小,free没有告诉我们应该释放多少个字节?那么free如何知道要释放多少呢?目前free参数只能知道从哪里开始释放。
申请的时候,实际申请的空间,会比你要的空间大一些,大出来的部分用来保存本次申请的“原信息”
原信息:属性信息(对应堆空间的大小等等)
4.堆空间适合大块空间的申请(因为还需要多申请空间保存属性信息)
5.free究竟干了什么工作?
取消指针内部保存的地址和堆空间的关系,但指针的值是不变的,所以建议在用free释放空间之后,将指针置为NULL。
动态内存函数的介绍
malloc和free
C语言提供的动态内存开辟函数
void* malloc(size_t size);
void free (void* ptr);
calloc
void* calloc(size_t num,size_t size);
realloc
realloc函数可以对已经申请内存的大小进行调整。
void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址 ;size 调整之后新大小 ;返回值为调整之后的内存起始位置。
常见的动态内存错误
一、对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
三、对非动态开辟的空间进行free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
四、使用free释放内存空间的一部分(必须整体申请,整体释放)
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
比
五、对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
六、动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
笔试题
2.题目二
char *GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
printf(str);
}
函数在栈上开辟,函数调用完之后被释放了,所以没有返回值。
注:计算机之中释放空间不会将数据清空(设置数据无效),所以str还是指向hellow world
由上图可知,函数已经执行完了,但是hellow world还是存在,那么为什么不输出str呢?
那是因为printf也是函数,被调用也是需要在栈上开辟空间的,所以他会将原本保存hellow world数据的空间覆盖,所以无法输出。
3.题目三:
void GetMemory(char **p, int num) {
*p = (char *)malloc(num);
}
void Test(void) {
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
没有判断参数的合法性,并且用malloc申请的空间没有释放(有可能会造成内存泄漏!!)。
4.题目四:
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
free对空间进行释放之后,str指针不等于NULL,但是空间却不可以用了,所以一般建议在进行空间释放之后,将指针置为NULL。
柔性数组
也许你从来没有听说过 柔性数组( flflexible array ) 这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员 报错的话将其改为int a[];
}type_a;
柔性数组的特点:结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
柔性数组的使用
//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++) {
p->a[i] = i; }
free(p);
柔性数组的优势
上述代码也可以写成:
//代码2
typedef struct st_type
{
int i;
int *p_a; }type_a;
type_a *p = malloc(sizeof(type_a));
p->i = 100; p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++) {
p->p_a[i] = i; }
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free ,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 free 就可以把所有的内存也给释放掉。