• 动态内存分分配(malloc、free、calloc、realloc)


    int val = 20; // 在栈空间上开辟4个字节
    char arr[10] = {0}; // 在栈空间上开辟10个字节的连续空间
    但是上述的开辟空间的方式有两个特点:
    • 空间开辟大小是固定的。
    • 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
    但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时候就需要动态内存开辟了

    1. malloc 和free函数 

    void *malloc( size_t size ); //开辟一定字节的内存空间并返回起始位置,若没有足够内存空间,返回NULL 
    • 如果开辟成功,则返回一个指向开辟好空间的指针。
    • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
    • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
    • 如果参数 size 0malloc的行为是标准是未定义的,取决于编译器。

    C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
    void free (void* ptr);

    1. int* p = (int* ) malloc(10*sizeof(int));
    2. //int* p = (int* ) malloc(INT_MAX);//开辟不成功的情况
    3. if(p == NULL)
    4. printf("%s\n", strerror(errno));//返回错误信息
    5. else
    6. {
    7. //正常使用空间
    8. int i = 0;
    9. for(i=0; i<10; i++)
    10. *(p+i) = 2*i;
    11. }
    12. //当申请空间不再使用时,就应该还给操作系统
    13. free(p);//释放内存空间;
    14. p = NULL;
    15. //malloc 和 free要成对使用;

    2. calloc函数

    void* calloc (size_t num, size_t size); // 开辟一块内存空间,并且把空间的每个字节初始化为0。

    • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
    • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。 
    1. int *p = (int*)calloc(10, sizeof(int));
    2. //int* p = (int* ) malloc(10*sizeof(int));//对比malloc的使用
    3. if(NULL != p)
    4. {
    5. //使用空间
    6. int i = 0;
    7. for(i=0; i<10; i++)
    8. printf("%d ", *(p+i));
    9. }
    10. free(p);
    11. p = NULL;

    3. realloc函数,对内存的大小进行调整

    void* realloc (void* ptr, size_t size); // 对ptr地址重新开辟空间size个字节,并返回一个地址

    • ptr 是要调整的内存地址
    • size 调整之后新大小
    • 返回值为调整之后的内存起始位置。
    • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。
    • realloc在调整内存空间的是存在两种情况:
      情况1:原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。返回地址为原有的ptr
      情况2:原有空间之后没有足够大的空间,在堆空间上另找一个合适大小的连续空间来使用,并把原来的数据拷贝过来。这样函数返回的是一个新的内存地址。且原有的地址空间被释放;
    1. int *ptr = (int*)malloc(100);
    2. int * p = NULL;
    3. if(ptr == NULL)
    4. printf("%s", strerror(errno));
    5. else
    6. {
    7. ;//正常使用空间
    8. }
    9. //p = (int *)realloc(ptr, 101);//此时p和ptr相同
    10. p = (int *)realloc(ptr, 1000);//此时p与ptr不同,是一块新的地址;
    11. if(p != NULL)//防止新地址开辟失败,所以不能直接放到ptr中
    12. {
    13. ptr = p;//这块空间始终由ptr来维护
    14. }
    15. free(ptr);//使用结束后释放ptr
    16. //注意:这列的p是临时变量,不是动态空间,函数结束就自动释放了,所以不需要free
    17. ptr = NULL;

        realloc也能直接开辟空间
        int* p = (int* )realloc(NULL, 40);//等价于malloc(40);

    4. 常见的动态内存错误

    4.1 对NULL指针的解引用操作

        int *p = (int *)malloc(INT_MAX/4);
        *p = 20;//如果p的值是NULL,就会有问题
        free(p);

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

    1. int i = 0;
    2. int *p = (int *)malloc(10*sizeof(int));
    3. if(NULL == p){
    4. exit(EXIT_FAILURE);
    5. }
    6. for(i=0; i<=10; i++){
    7. *(p+i) = i;//当i是10的时候越界访问
    8. }
    9. free(p);

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

        int a = 10;
        int *p = &a;
        free(p);//ok?

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

        int *p = (int *)malloc(100);
        p++;
        free(p);//p不再指向动态内存的起始位置

    4.5 对同一块动态内存多次释放

        int *p = (int *)malloc(100);
        free(p);
        //可以在这里加一个 p = NULL;
        free(p);//重复释放

    4.6 动态开辟内存忘记释放(内存泄漏)

        while(1)
        {
            int* p = (int*)malloc(1);
        }//这里没有动态内存释放,这个程序就会造成一直内存泄漏;

    5. 几个经典的笔试题

    5.1 传值问题

    1. void GetMemory(char *p)
    2. {
    3. p = (char *)malloc(100);
    4. }
    5. void Test(void)
    6. {
    7. char *str = NULL;
    8. GetMemory(str);
    9. strcpy(str, "hello world");
    10. printf(str);
    11. }
    12. //以上代码存在两个问题
    13. //1.str传值时候,对p赋值不会改变str;
    14. //2.malloc开辟了空间,最后没有释放,造成内存泄漏;
    15. //改进方法一:采用传地址,输出型参数
    16. void GetMemory(char **p)
    17. {
    18. *p = (char *)malloc(100);
    19. }
    20. void Test(void)
    21. {
    22. char *str = NULL;
    23. GetMemory(&str);
    24. strcpy(str, "hello world");
    25. printf(str);
    26. free(str);
    27. str = NULL;
    28. }
    29. //改进方法二:函数返回
    30. char* GetMemory(char* p)
    31. {
    32. p = (char *)malloc(100);
    33. return p;//这里因为p是堆区开辟的数据,可以返回,后面可以用引用
    34. }
    35. void Test(void)
    36. {
    37. char *str = NULL;
    38. str = GetMemory(str);
    39. strcpy(str, "hello world");
    40. printf(str);
    41. free(str);
    42. str = NULL;
    43. }

    5.2 返回栈空间地址,非法访问

    1. char *GetMemory(void)
    2. {
    3. char p[] = "hello world";
    4. return p;
    5. }
    6. void Test(void)
    7. {
    8. char *str = NULL;
    9. str = GetMemory();
    10. printf(str);//打印出来应该是随机值,p数组出函数就销毁了,只返回了一个地址;
    11. }

    5.3 free之后及时置为NULL

    1. void Test(void)
    2. {
    3. char *str = (char *) malloc(100);
    4. strcpy(str, "hello");
    5. free(str);//释放后并不会把str置为NULL;应该加上 str = NULL;
    6. if(str != NULL)
    7. {
    8. strcpy(str, "world");//已经释放后,就造成非法访问内存;
    9. printf(str);
    10. }
    11. }
  • 相关阅读:
    MySQL查询性能优化七种硬核之索引下推
    AI工程化—— 如何让AI在企业多快好省的落地?
    Docker 问题记录
    web前端期末大作业 html+css+javascript汽车介绍网页设计实例 企业网站制作(带报告3490字)
    Carsim2019与Simulink (Matlab2018b)联合仿真成功(超详细)
    Java数据结构:前缀、中缀、后缀表达式与逆波兰计算器的实现
    适合开发者使用的6款浏览器,开发者工具很实用
    如何开启Win10虚拟机Hyper-V功能
    自动化测试必会之数据驱动测试
    UE5第一人称射击游戏蓝图教程
  • 原文地址:https://blog.csdn.net/m0_60416282/article/details/125534309