动态内存管理学习笔记

1、为什么存在动态内存分配

        int a = 10;
        int arr[10];

上述方式开辟的空间不可变,但有时我们需要的空间大小在程序运行的时候才知道

2、动态内存函数的函数

2.1、malloc和free

动态内存开辟    void* malloc(size_t size)

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

    如果开辟成功,则返回一个指向这块空间的指针;

    如果开辟失败,则返回NULL指针,因此malloc的返回值一定要做检查;

    返回值的类型是void*,所以malloc函数其实并不知道开辟空间的类型,具体在使用的时候使用者自己来决定;

    如果参数size为0,这种行为是标准未定义的;

int main()
{
    int* p = (int*)malloc(40);//malloc申请的空间大小单位为字节
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d\n", *(p + i));//malloc申请到空间后,直接返回这块空间的起始地址,不会初始化空间的内容
    }

    free(p);
    p = NULL;
    return 0;
}

malloc申请的内存空间,当程序退出时,还给操作系统,当程序不退出,动态申请的内存不会主动释放,需要使用free函数来释放

C语言提供另一个函数free,专门是用来做动态内存的释放和回收的

        void free(void* ptr)

        如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的

        如果参数ptr是NULL指针,则函数什么事都不做
 

int main()
{
    int arr[10] = { 0 };
    int* ptr = NULL;
    ptr = (int*)malloc(40);//将malloc所开辟空间返回的指针赋给ptr
    if (ptr != NULL)//检查ptr是否为空
    {
        int i = 0;
        for (i = 0; i < 10; i++)
        {
            *(ptr + i) = 0;//给开辟的空间赋值为0
        }
        for (i = 0; i < 10; i++)
        {
            printf("%d\n", *(ptr + i));
        }
    }
    free(ptr);
    ptr = NULL;//释放指针

    return 0;
}


2.2、calloc

calloc函数也用来动态内存分配

        void* calloc(size_t num,size_t size)

        函数的功能是为num个大小为size的元素开辟一块空间,并且把每个空间的每个字节初始化为0

        与函数malloc的区别只在于calloc会在返回地址之前把申请的每个空间自己初始化为0

int main()
{
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("calloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d\n", *(p + i));
    }
    free(p);
    p = NULL;
    return 0;
}

2.3、realloc

void realloc(void* ptr,size_t size);

        ptr是要调整的内存地址

        返回值为调整之后的内存起始位置

        这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc在调整内存空间时存在两种情况:

        1、原有空间之后有足够大的空间,直接加

        2、后面的空间不够:

                1、开辟新空间

                2、将旧的空间的数据拷贝到新的空间

                3、释放旧的空间

                4、返回新空间的起始地址

int main()
{
    int* p = (int*)malloc(40);//开辟40字节的空间
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)//赋值
    {
        p[i] = i;
    }
    int* ptr = (int*)realloc(p, 80);//在p原有基础上再增加80字节空间
    if (ptr != NULL)
    {
        p = ptr;
        ptr = NULL;
    }
    else
    {
        perror("realloc");
        return 1;
    }
    for (i = 0; i < 30; i++)
    {
        printf("%d\n", p[i]);
    }
    return 0;
}*/

3、常见的动态内存错误

3.1、对NULL指针的解引用操作

void test()
{
    int* p = (int*)malloc(INT_MAX / 4);
    *p = 20;//如果p的值是NULL,就会有问题,所以在使用前都需要进行检验
    free(p);
}

3.2、对动态开辟空间的越界访问

int main()
{
    int* p = (int*)malloc(40);//开辟40字节的空间
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 20; i++)//使用20个整型空间,即80字节
    {
        p[i] = i;
    }
    return 0;
}

3.3、对非动态开辟内存使用free释放

void main()
{
    int a = 10;
    int* p = &a;//p不是动态开辟的,是直接定义的
    printf("%d\n", *p);
    free(p);
    p = NULL;
    return 0;
}

3.4、使用free释放一块动态开辟内存的一部分

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        *p = i;
        p++;//p经过++,已经不再指向malloc开辟的40字节空间的开头了
    }
    free(p);
    p=NULL;
    return 0;
}

3.5、对同一块空间的多次释放

int main()
{
    int* p = malloc(40);
    if (p = NULL);
    {
        perror("malloc");
        return 1;
    }
    free(p);
    free(p);
    return 0;
}

3.6、忘记释放(内存泄漏)

        在函数中malloc100个字节的空间,并且把地址给p,当离开作用域后,p的值销毁,且开辟的空间没有释放,这100字节的空间就永久无法使用

        动态申请的内存空间,不会因为出了作用域自动销毁(还给操作系统)

        只有两种方式销毁:free;程序结束

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 20;
	}
}
void main()
{
	test();
	return 0;
}

练习:以下程序能否成功运行

1、

void GetMemory(char* p)
{
    p = (char*)malloc(100);//空间开辟后没有释放,造成内存泄漏
}
void Test(void)
{
    char* str = NULL;
    GetMemory(str);//此处的传值并不能改变str指向的内容,仍然是空指针
    strcpy(str, "hello world");
    printf(str);
}

int main()
{
    Test();
    return 0;
}

修改为以下即可

void GetMemory(char** p)//使用二级指针接收
{
    *p = (char*)malloc(100);
}
void Test(void)
{
    char* str = NULL;
    GetMemory(&str);//函数传指针str的地址
    strcpy(str, "hello world");
    printf(str);
    free(str);//释放空间
    str = NULL;
}

int main()
{
    Test();
    return 0;
}

 2、

char* GetMemory(void)
{
    char p[] = "hello world";//将数组创建为static变量即可
    return p;
}
void Test(void)
{
    char* str = NULL;
    str = GetMemory();//此处str可以拿到GetMemory的返回值p的地址,但是p地址指向空间的内容离开GetMemory函数后已经销毁了,p也变成了野指针
    printf(str);
}
int main()
{
    Test();
    return 0;
}

3、

void GetMemory(char** p, int num)
{
    *p = (char*)malloc(num);
}
void Test(void)
{
    char* str = NULL;
    GetMemory(&str, 100);//代码可以正常运行,但是没有free
    strcpy(str, "hello");
    printf(str);
}

int main()
{
    Test();
    return 0;
}

4、能否运行

void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);//str释放后空间已经还给操作系统,后面的使用为非法访问,在释放后应该立马置空NULL
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

int main()
{
    Test();
    return 0;
}

4、柔性数组

4.1、柔性数组的特点

        结构中的柔性数组成员前面必须要有至少一个成员

        sizeof返回的这种结构大小不包含柔性数组的内存

        包含柔性数组成员的结构一般用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

struct S
{
    int n;
    int arr[0];//柔性数组
};
int main()
{
    //printf("%d\n", sizeof(struct S));//打印4
    struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);//malloc开辟44字节空间给结构指针ps
    if (ps == NULL)
    {
        perror("malloc");
        return 1;
    }
    ps->n = 1;//结构指针ps元素n赋值为1
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        ps->arr[i] = i;
    }
    
    struct S* ptr = (struct S*)realloc(ps, 80);
    if (ptr == NULL)
    {
        perror("realloc");
        return 1;
    }
    ps = ptr;
    ps->n = 2;
    for (i = 0; i < 15; i++)
    {
        printf("%d\n", ps->arr[i]);
    }
    free(ps);
    ps = NULL;
    return 0;
}

另一种实现方式

struct S
{
    int n;
    int* arr;
};
int main()
{
    struct S* ps = (struct S*)malloc(sizeof(struct S));
    if (ps == NULL)
    {
        perror("malloc");
        return 1;
    }
    ps->n = 1;
    ps->arr = (int*)malloc(40);
    if (ps->arr == NULL)
    {
        perror("malloc");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        ps->arr[i] = i;
    }
    int* ptr = realloc(ps->arr, 80);
    if (ptr == NULL)
    {
        perror("realloc");
        return 1;
    }
    ps->arr = ptr;
    for (i = 0; i < 15; i++)
    {
        printf("%d\n", ps->arr[i]);
    }
    free(ps);
    ps = NULL;
    free(ps->arr);
    ps->arr = NULL;
    return 0;
}

上述两段代码虽然实现的功能相同,但是第一段有更多好处

        1、内存分配的次数少,需要释放的次数就少

        2、有利于访问速度,连续的内存可以减少内存碎片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值