动态内存管理的一些坑

为什么要使用动态内存 ?

之前我们学到的内存开辟方式是在栈上开辟一段空间大小固定,地址连续的内存空间。如果我们申请了100个字节的内存空间,但是我们最后只用了10个字节,这样就会极大的浪费我们的内存空间。更重要的是我们使用静态连续的地址空间时,如果我们输入的元素超过了数组的长度,有些编译器也不会报错,它会把数组存满,剩下的元素就是随机的值。
动态内存管理就是解决在运行时才知道所需的空间大小的数组的内存分配。

  • 了解动态内存管理,首先先了解内存中的数据是怎么存放的。
    动态内存管理的一些坑动态内存管理的第一坑
    • malloc
      • 当程序调用malloc时,,malloc会从内存池中提取出一块合适的且地址连续 的内存(没有初始化),如果内存池满了,则会提取失败,此时malloc会返回一个空指针。所以我们在使用malloc的时候要对malloc判空,这个地方对于我们来说是经常遗忘的。
      • 如果malloc申请内存成功了,则会返回一个指针。这个指针的类型是由我们自己决定,所以我们在使用的时候要对其进行强制类型转换。
    • free
      • free函数针对的是动态开辟的空间,如果你用free函数去释放静态空间,那么就会出错。所以free的参数只能是malloc,calloc,realloc,NULL的返回值。
      • free释放内存之后,依然指向被释放的内存的起始位置,此时这个指针是一个野指针,我们要手动的将其赋为空。
    • calloc
      • 使用calloc实现动态内存分配的时候,在返回首地址之前,calloc提取的空间会被初始化为0.
    • realloc
      • realloc一定是跟malloc搭配使用。realloc主要是针对malloc申请的空间太小了或者太大,想要对原内存进行扩容或者缩小。
      • 扩容:四步:1、开辟新空间 (一般是扩容原空间大小的两倍)2、拷贝元素 3、释放旧空间 4、更新参数(使用新开辟空间的地址)
      • 缩小:realloc会将原有内存尾部的部分内存释放掉。

总结

  • malloc申请空间之后要进行检查,看是否为空。在用完要手动进行释放。
  • 使用free释放内存之后,此时的指针是一个野指针。如果你去访问它,则是访问非法内存。
  • malloc与free总是成对出现的。
  • 动态开辟空间越界访问也是一个常见的错误,如你申请的数组arr[3],而如果你引用的时候下标小于0或者大于2就会发生越界。

几个开胃菜

  • 找出题中的错误
    void test(){
    int* p = (int*)malloc(100);
    p++;
    free();//未定义行为
    }
    本题中指针变量p指向malloc申请之后内存的起始位置,然后程序往下走的时候指针变量往后移动了一步,此时的p是指向内存的下一个位置。而free传入的地址必须是malloc返回的起始地址,而这个p是起始地址的下一个位置,所以是未定义行为。
  • 运行Test函数 会有什么样的结果?
    char* GetMemory(void){
    char p[] = “hello world”;
    return p;
    }
    void Test(void){
    char* str = NULL;
    str = GetMemory();
    printf(str);//访问非法内存
    }
    本题char p[]就是把字符串常量"hello world"往数组中拷贝了一份。p是一个局部变量,其生命周期会随着函数执行结束内存就释放了,在函数外部就不能访问p中的内容。但是如果把char p[] = “hello world”;换成char* p = “hello world”;或者 改成static char p[] = “hello world”;这道题就完美解决了。原因是字符串常量"helllo world"的生命周期是跟随着整个程序的,char* p是一个局部变量,它存放的是字符串常量的地址。当函数执行结束时把这个地址交给了str,此时拿着这个地址就能找到字符串的内容。