C语言 - 动态内存分配

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

一、内存分区
栈区:局部变量,函数形参
堆区:动态内存开辟(malloc/free/calloc/realloc)
静态区:全局变量,静态变量

二、动态内存开辟函数介绍
1.malloc
2.free

int main()
{
    //假设开辟10个整型空间
    int arr[10];  //栈区
    //动态内存开辟的
    int* p = (int*)malloc(10 * sizeof(int));  //堆区
    //使用这些空间的时候
    if (p == NULL)
    {
        perror("main");  //main:xxxxxx
        return 0;
    }
    //使用
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (i = 0; i < 10; i++)
    {
        printf("%d\n", p[i]);
    }
    //回收空间
    free(p);
    //自己动手把p置为NULL
    p = NULL;
    return 0;
}


int main()
{
    int a = 10;
    int* pa = &a;
    //free(pa);  //err!free函数只能释放堆区动态开辟的空间
    free(NULL);  //什么事都不做
    return 0;
}


3.calloc
与malloc的区别
(1)参数有两个,为num个大小为size的元素开辟一片空间
(2)把空间的每个字节初始化为0
int main()
{
    //int* p = malloc(10 * sizeof(int));
    //if (p == NULL)
    //    return 1;
    //int i = 0;
    //for (i = 0; i < 10; i++)
    //{
    //    printf("%d\n", *(p + i));   //都是随机值
    //}
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
        return 1;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d\n", *(p + i));   //都是被初始化过的0
    }
    return 0;
}


4.realloc
void* realloc(void* memblock, size_t size)
第一个参数为指向要调整空间的指针,第二个参数为调整后的开辟内存大小
有可能返回旧的地址(原有空间后面足够),有可能返回新的地址(空间不足,找到了新空间),有可能返回NULL(无法开辟)

int main()
{
    int* p = calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = 5;
    }
    //还需要p指向的空间更大,需要20个int的空间
    //realloc调整空间
    int* ptr = (int*)realloc(p, 20 * sizeof(int));
    if (ptr)
    {
        p = ptr;
    }
    //如果memblock之后的内存不够存放要开辟的空间
    //(1)在内存中重新找一块新的空间
    //(2)把原本空间内容拷贝到新的空间
    //(3)释放原本空间
    //(4)返回新空间起始地址

    //如果在内存其他位置也不够存放要开辟的空间
    //realloc有可能找不到合适的空间来调整大小,这时返回NULL,因此不直接把返回值赋给原指针

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

int main()
{
    int* p = (int*)realloc(NULL,sizeof(int)*10);  //这里功能类似于malloc,就是直接在堆区开辟40个字节
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d\n", *(p + 1));  //随机值
    }
    free(p);
    return 0;
}

三、动态内存开辟常见的错误

1.对NULL指针进行解引用操作
int main()
{
    int* p = (int*)malloc(10000000000 * 10000000000);
    //要对malloc返回值做判断!  if(p==NULL)
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i;  //无法解引用空指针和空指针+i
    }
    free(p);
    return 0;
}

2.动态开辟空间的越界访问
int main()
{
    int* p = (int*)malloc(10 * sizeof(int));
    if (p == NULL)
    {
        return 1;
    }
    int i = 0;
    for (i = 0; i < 40; i++)  //误以为开辟了40个元素,其实是40个字节
    {
        *(p + i) = i;
    }
    free(p);
    p = NULL;
    return 0;
}


3.使用free释放非动态开辟的空间
int main()
{
    int arr[10] = { 0 };  //栈区
    int* p = arr;
    //使用
    free(p);
    p = NULL;
    return 0;
}


4.使用free只释放动态内存的一部分
int main()
{
    int* p = (int*)malloc(10 * sizeof(int));
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        *p++ = i;
    }
    free(p);   //err!首先,找不到p原本的位置;其次,只释放动态内存的一部分
    p = NULL;
    return 0;
}


5.对同一块动态开辟的空间多次释放 - 内存泄漏 - 比较严重的
int main()
{
    int* p = (int*)calloc(100,sizeof(int));
    //使用
    //释放
    free(p);
    //为了避免err,释放完p后要置为NULL
    //再次释放
    free(p);
    return 0;
}


6.动态开辟的空间忘记释放
void test()
{
    int* p = (int*)malloc(100);
    if (p == NULL)
    {
        return;
    }
    //使用
}
int main()
{
    test();
    //出了函数后,函数内部创建栈顶的p销毁,但其开辟的空间没有销毁,再也没法找到这块空间
    //....
    return 0;
}

动态开辟的空间,两种回收方式
1.主动free
2.程序结束 - 如果程序长期运行不结束,内存泄漏可能导致内存空间用完,因此要记得用完后主动free开辟的动态内存


练习题
1.
void GetMemory(char* p)
{
    p = (char*)malloc(100);   //形参p是str的一份临时拷贝,在GetMemory函数内部申请动态内存,存放在p中,不会影响外面的str
}  //GetMemory函数返回时形参p销毁,使得动态开辟的100个字节存在内存泄漏
void Test(void)
{
    char* str = NULL;
    GetMemory(str);   //把NULL传参,是值传递,形参是临时拷贝
    strcpy(str, "hello world");   //相当于还是把helloworld拷贝给空指针
    printf(str);
    free(str);
    str = NULL;
}
int main()
{
    Test();
    return 0;
}

修改:
char* GetMemory1()
{
    char* p = (char*)malloc(100);
    return p;
}

void GetMemory2(char** p)
{
    *p = (char*)malloc(100);
}

void Test(void)
{
    char* str1 = NULL;
    str1 = GetMemory1();
    strcpy(str1, "hello world");
    printf(str1);  //写法正确!
    //printf("hello world");  //const char* p = "hello world",传给printf的实际上是h的地址
    free(str1);
    str1 = NULL;
    puts("\n");

    char* str2 = NULL;
    GetMemory2(&str2);
    strcpy(str2, "hello world");
    puts(str2);
    free(str2);
    str2 = NULL;
}
int main()
{
    Test();
    return 0;
}


2.
GetMemory内部创建的数组是在栈上的,出了函数p数组的空间就还给了操作系统
返回的地址没有实际意义,如果通过返回的地址去访问内存就是非法访问内存
char* GetMemory(void)
{
    char p[] = "hello world";
    return p;   //返回栈空间地址的问题
}
void Test(void)
{
    char* str = NULL;
    str = GetMemory();
    printf(str);
}
int main()
{
    Test();
    return 0;
}

int *f2(void)
{
    int* ptr;
    *ptr = 10;  //没有初始化,野指针
    return ptr;
}
int main()
{
    int* a = f2();
    printf("%d\n", a);
}


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);   //没有free(str)
}
int main()
{
    Test();
    return 0;
}


4.
void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);  //free之后要置空!
    if (str != NULL)  //str没有置空,仍然指向已经被free过的空间,非法访问
    {
        strcpy(str, "world");
        printf(str);
    }
}

#define INT_PTR int*
typedef int* int_ptr;
INT_PTR a, b;  //int* a,b  - 指针变量不能连续定义,除非int* a,*b!
int_ptr c, d;  //typedef重命名一种完整类型,是独立的


C/C++中程序内存区域划分

内核空间(用户代码不能读写)

栈(向下增长)

内存映射段(文件映射、动态库、匿名映射)

堆(向上增长)

数据段(全局数据、静态数据) - 静态区

代码段(可执行代码/只读常量)

四、柔型数组
C99中,结构的最后一个成员允许是未知大小的数组,这就叫做[柔型数组]成员

特点:
1.柔型数组成员之前必须至少有一个成员
2.sizeof返回的这种结构大小不包括柔型数组的大小
3.包含柔型数组成员的结构用malloc()函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔型数组的预期大小
struct S
{
    int n;
    int arr[];  //大小是未知
};

struct S
{
    int n;
    int arr[0];  //大小是未知
};

int main()
{
    //printf("%d\n", sizeof(struct S));  //4
    
    //如果期望arr大小是10个整型
    struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));   //malloc开辟的空间可以用calloc重新分配,因此有"柔性"
    if (ps != NULL)
    {
        ps->n = 10;
        int i = 0;
        for (i = 0; i < 10; i++)
        {
            ps->arr[i] = i;
            printf("%d ", ps->arr[i]);
        }
        printf("\n");
    }
    //增容
    struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20*sizeof(int));
    //使用
    if (ptr)
    {
        int i = 0;
        for (i = 0; i < 20; i++)
        {
            ptr->arr[i] = i;
            printf("%d ", ptr->arr[i]);
        }
    }
    //释放 - 二者指向同一片空间
    free(ps);
    ps = NULL;
    ptr = NULL;
    return 0;
}


struct S
{
    int n;
    int* arr;
};

int main()
{
    struct S* ps = (struct S*)malloc(sizeof(struct S));
    if (ps == NULL)
        return 1;
    ps->n = 10;
    ps->arr = malloc(10*sizeof(int));
    if (ps->arr == NULL)
        return 1;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(ps->arr + i) = i;
    }
    //增加
    int* ptr = (int*)realloc(ps->arr, 20 * sizeof(int));
    if (ptr != NULL)
    {
        ps->arr = ptr;
        for (i = 0; i < 20; i++)
        {
            ps->arr[i] = i;
        }
    }
    //释放
    free(ps->arr);
    ps->arr = NULL;
    free(ps);
    return 0;
}

柔型数组与指针成员相比优点:
1.指针成员需要两次malloc,两次free,容易出错
  柔型数组方便内存释放
2.堆申请的空间之间有内存碎片,内存碎片的存在让内存利用率降低(空间局部性)
  柔型数组连续的内存有益于提高访问速度,也有益于减少内存碎片

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值