Bootstrap

C语言——malloc函数详解

引言:为什么使用动态内存分配

当你声明数组时,你必须用一个编译时常量指定数组的长度。但是,数组的长度常常在运行时才知道,这是由于它所需要的内存空间取决于输入数据。例如,一个用于计算学生等级和平均分的程序可能需要存储一个班级所有学生的数据,但不同班级的学生数量可能不同。在这些情况下,我们通常采用的方法是声明一个较大的数组,它可以容纳可能出现的最多元素。

这种方法简单,但也有好几个缺点:

  • 缺点1、这种声明在程序中引入了人为的限制,如果程序需要使用的元素数量超过了声明的长度,它就无法处理这种情况。要避免这种情况,显而易见的方法是把数组声明的更大一些,但这种做法使他的第2个缺点进一步恶化。
  • 缺点2、如果程序实际需要的元素数量比较少时,巨型数组的绝大部分内存空间都被浪费了。
  • 缺点3、如果输入的数据超过了数组的容纳范围时,程序必须以一种合理的方式作出响应,它不应该由于一个异常而失败。但也不应该打印出看上去正确实际上却是错误的结果。

malloc函数和free函数头文件

#include<stdlib.h> 或者 #include<malloc.h>

原型

extern void * malloc(unsigned int num_byte);      

  • 功能:分配长度为num_byte字节的内存块
  • 参数:需要分配的内存字节数,如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针
  • 返回值:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

当内存不使用时,应使用free()函数将内存块释放

注意:malloc所分配的是一块连续的内存。例如,如果请求它分配100个字节的内存,那么它实际分配的内存就是100个连续的字节,并不会分开位于两块或者多块不同的内存。

void free( void * pointer);

  • 功能:释放内存
  • 参数:free函数的参数要么是NULL,要么是一个先前从malloc、calloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。
  • 返回值:无

简单举例:

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

int main()
{
    char *p;
    p = NULL;
    p = (char *)malloc(100);

    if (p != NULL) // 如果内存分配成功
    {
        printf("memory allocated at:%x\n", p);
    }
    else
    {
        printf("not enough memory!\n");
    }
    free(p);
    p = NULL;

    return 0;
}

函数声明(函数原型)

void * malloc(int size);

  • 说明:malloc向系统申请分配指定size个字节的内存空间。返回类型是void *类型。void *类型表示未确定类型的指针。C、C++规定,void *类型可以强制转换为任何其他类型的指针。

 例如:

int * p;

p = (int *)malloc(sizeof(int));

  • 第一、malloc函数返回的是void *类型,如果你写成:p = malloc(sizeof(int));则程序无法通过编译,报错:“不能将void *赋值给int *类型变量”。所以必须通过(int *)来强制类型转换
  • 第二、函数的实参为sizeof(int),用于指明一个整形数据需要的大小,如果你写成:

             int * p =(int *)malloc(1);

代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里面存入一个整数时,会有3个字节无家可归,而直接“住进领居家”!造成的后果是后面的内存中原有数据内用全部被清空。

比如你想分配100个int类型的空间:

int* p = (int *) malloc ( sizeof(int) * 100);//分配可以放得下100个整数的内存空间

malloc函数程序示例

typedef struct data_type
{
    int age;
    char name[20];
} data;

data *bob = NULL;
// 定义一个指针,指向NULL
bob = (data *)malloc(sizeof(data));
// 内存分配

if (bob != NULL) // 检测内存是否分配成功
{
    bob->age = 22;
    strcpy(bob->name, "Robert");

    printf("%sis%dyearsold\n", bob->name, bob->age);
}
else
{
    printf("mallocerror!\n");
    exit(-1);
}
free(bob);  // 释放内存
bob = NULL; // 内存指向NULL

常见的动态内存错误

  1. 对NULL指针进行解引用操作(忘记检查所请求的内存是否分配成功)
  2. 对分配的内存进行操作时越过了分配内存的边界
  3. 释放并非动态分配的内存(传递给free的指针必须是一个从malloc、calloc或realloc函数返回的指针)
  4. 试图释放一块动态分配的内存的一部分(释放一块内存的一部分是不允许的,动态分配的内存必须整块一起释放)
  5. 一块动态内存被释放之后被继续使用等。(不要访问已经被free函数释放了的内存,注意指针的复制)

内存泄漏

分配内存但在使用完毕后不释放将引起内存泄漏。内存泄漏会增加程序的体积,有可能导致程序或系统的崩溃。

总结、函数使用的注意事项:

  • 申请了内存空间后,必须检查是否分配成功。
  • 当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
  • 内存分配和内存释放这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
  • 虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。
  • malloc只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的
  • malloc()函数其实就在内存中找一片指定大小的空间,然后将这个空间的首地址返回给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是数组的首地址,这要看malloc()函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以连续也可以不连续。对于我们程序员来说,我们关注的是逻辑上的连续,因为操作系统会帮我们安排内存分配,所以我们使用起来就可以当做是连续的
  • exit(1):退出整个程序,终止进程,并返回1给操作系统。由于返回0代表程序正常退出,返回1等其他数字通常代表异常终止。可通过返回的具体数值判断出错源

编程提示

  • 数组被声明时,必须在编译时知道它的长度。动态内存分配允许程序为一个长度在运行时才知道的数组分配内存空间
  • 检查malloc函数返回的指针是否为NULL
  • 动态内存分配有助于消除程序内部存在的限制
  • 使用sizeof计算数据类型的长度,提高程序的可移植性。
;