C语言——动态内存管理(malloc, calloc, realloc, free, 柔性数组详解)

简介: C语言——动态内存管理(malloc, calloc, realloc, free, 柔性数组详解)

C语言——动态内存管理

1. 为什么需要动态内存管理

我们以往定义数组,都是这么定义的:

int nums[10] = {0};

以这种方式开辟空间有两个特点:

  1. 空间开辟的大小是固定的
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配

因此就导致了这样一个现象:我们无法在后续的过程中修改数组的大小,这是一个十分麻烦的事情

而为了解决这个问题,我们就需要学习动态内存开辟了

2. 动态内存函数的介绍

注:需要头文件<stdlib.h>

需要知道,和静态开辟空间不一样,计算机是在堆上开辟的动态空间

2.1 malloc

void* malloc (size_t size);

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

  • 如果开辟成功,则返回一个指向这块空间的指针
  • 如果开辟失败,则返回一个空指针(NULL),因此我们一定要对malloc的返回值作有效性的判断
  • 返回值为void *,因此当我们用指针变量接受这个返回值时,我们要将这个返回值强制转换为需要的类型
  • 如果参数size为0,malloc的行为是标准未定义的,

例如:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
    int *nums = (int*)malloc(sizeof(int) * 10); 
    //检验返回值的有效性
    if (nums == NULL)
    {
        perror("malloc");
        return 1;
    }
    //循环打印nums指向空间的值
    for (int i = 0; i < 10; i++)
        printf("%d\n", *(nums + i));
    free(nums);
    nums = NULL;
    return 0;
}

output:

-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
  • 这说明,malloc申请到空间后,是不会对该空间初始化的

2.2 free

需要注意:凡是动态申请的内存,除非整个程序结束,申请的内存是不会主动归还给系统的,为了避免内存泄漏,我们应该使用函数free来将申请的内存释放

void free (void* ptr);

free函数用来释放动态开辟的内存

  • 如果参数``ptr指向的空间不是动态开辟的,那free`函数的行为是未定义的
  • 如果参数ptr是NULL指针,则函数什么事都不做
  • 正常释放后,ptr指向不明,称为野指针,因此,应该置为空(NULL)

例如:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
    int *nums = (int*)malloc(sizeof(int) * 10); 
    //检验返回值的有效性
    if (nums == NULL)
    {
        perror("malloc");
        return 1;
    }
    free(nums); //释放ptr所指向的动态内存
    nums = NULL;  //将野指针置空
    return 0;
}

2.3calloc

void* calloc (size_t num, size_t size);
  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • 与函数malloc的区别只在于calloc会在返回指针之前把申请的空间的每个字节初始化为0

例如:

##include<stdio.h>
#include<stdlib.h>
int main()
{    
  //申请10个大小为4的空间,并让指针nums指向它
    int* nums = (int*)calloc(10, sizeof(int));
    //判断返回值的有效性
    if(nums == NULL)
    {
        perror("calloc");
        return 1;
    }
    //打印nums指向空间的元素
    for (int i = 0; i < 10; i++)
        printf("%d\n", *(nums + i));
    free(nums);
    nums = NULL;
    return 0;
}

output:

0
0
0
0
0
0
0
0
0
0

2.4 realloc

所谓的动态内存管理,“内存管理”我们好像已经会了,那这个“动”又是怎么做到的呢?我们前面所学的malloc, calloc好像并不能让申请的内存动起来呀。

要想实现对内存的增加或减小,就需要我们的函数realloc

void* realloc (void* ptr, size_t size);
  • ptr是要调整的内存地址
  • 如果ptr不为空,那么就会修改ptr所指向空间的大小
  • 如果ptr为空,那么就和malloc的功能相似,会直接返回一个指向大小为size字节空间的指针
  • size为调整之后的大小
  • 返回值为调整之后的内存的起始位置
  • 扩容后的空间不会被初始化
  • 这个函数调整源内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
  • ealloc在调整内存空间时存在两种情况:
  • 情况一:原有空间之后有足够大的空间,那么就在原有的地方增容,并返回原来的起始地址

  • 情况二:原有空间之后没有足够大的空间,那么就在合适的地方重新开辟一块大小为size的空间,将原来空间的数据拷贝到新空间,再释放掉原来的空间,最后再返回新空间的起始地址

  • 如果调整失败,就会返回空指针,为了考虑到这种情况,我们应该避免以下的代码:
//error example
#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* nums = (int*)malloc(10 * sizeof(int));
  if (nums == NULL)
  {
    perror("malloc");
    return 1;
  }
    //如果开辟失败,那么nums就成了空指针,前面nums管理的40个字节的空间就找不到了,这样就造成了内存泄漏
  nums = (int*)realloc(nums, 20 * sizeof(int));
  if (nums == NULL)
  {
    perror("realloc");
    return 1;
  }
  free(nums);
  nums = NULL;
  return 0;
}
  • 正确的方式应该是这样的:
//right example
#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* nums = (int*)malloc(10 * sizeof(int));
  if (nums == NULL)
  {
    perror("malloc");
    return 1;
  }
    //先用一个中间变量temp接受
  int* temp = (int*)realloc(nums, 20 * sizeof(int));
  if (temp == NULL)
  {
    perror("realloc");
    return 1;
  }
    //当temp有效时,再用nums接受
    nums = temp;
  free(nums);
  nums = NULL;
  return 0;
}

最后,再对realloc的具体使用举个例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //先动态开辟40个字节的内存
  int* nums = (int*)malloc(10 * sizeof(int));
  if (nums == NULL)
  {
    perror("malloc");
    return 1;
  }
    //将这块空间初始化为0
  memset(nums, 0, 10 * sizeof(int));
    //将这块空间扩容到60个字节,并先用中间变量temp接受
  int* temp = (int*)realloc(nums, 15 * sizeof(int));
  if (temp == NULL)
  {
    perror("realloc");
    return 1;
  }
    //确定temp有效后再用nums指向temp
  nums = temp;
    //打印扩容后空间的数据
  for (int i = 0; i < 15; i++)
  {
    printf("%d\n", nums[i]);
  }
    //释放内存
  free(nums);
  nums = NULL;
  return 0;
}

output:

0
0
0
0
0
0
0
0
0
0
-842150451
-842150451
-842150451
-842150451
-842150451

3. 常见的关于动态内存开辟的错误

3.1 对NULL指针的解引用操作

void test()
{
    int *p = (int *)malloc(sizeof(int));
    *p = 20;  //如果p为空指针,就会有问题,一定先要检查返回指针的有效性
    free(p)
}

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

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
  }
    for(int i = 0; i <= 10; i++)
        *(p + i) = i; //当i是10的时候就会越界访问
    free(p);
}

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

void test()
{
    int nums[10] = {0};
    free(nums);
}

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

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
  }
    free(p);
    free(p);
}

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

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
  }
    for(int i = 0; i < 10; i++)
        *(p + i) = i; 
}

4. 柔性数组(flexible array)

C99中,结构体中的最后一个元素允许是位置大小的数组,这就叫做柔性数组

例如:

typedef struct ST
{
  int i;
  int a[0]; //柔性数组成员,也可以写成 a[];
}ST;

4.1 柔性数组的特点

  1. 柔性数组前至少有一个其他成员
  2. sizeof返回的结构体大小不包含结构中柔性数组的内存,例如对于上面的代码:
printf("%d\n",sizeof(ST));
  1. output:
4
  1. 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,同样,也可以用realloc进行增容,例如:
#include<stdio.h>
#include<stdlib.h>
typedef struct ST
{
  int i;
  int a[];  //柔性数组成员
}ST;
int main()
{
  ST *st1 = (ST*)malloc(sizeof(ST) + sizeof(int) * 10); //向内存申请大小为结构大小加10个整型的内存空间
  if (NULL == st1)
  {
    perror("malloc");
    return 1;
  }
  st1->i = 10;
    //给空间赋值
  for (int i = 0; i < 10; i++)
    (st1->a)[i] = i + 1;
  //打印空间元素
  for (int i = 0; i < 10; i++)
    printf("%d\n", (st1->a)[i]);
    //增容,将内存扩大5个int型
    ST* temp = (ST*)realloc(st1, sizeof(ST) + sizeof(int) * 15);
  if (NULL == temp)
  {
    perror("realloc");
    return 1;
  }
    st1 = temp;
    //打印空间数据
  for (int i = 0; i < 15; i++)
    printf("%d\n", (st1->a)[i]);
    //释放动态内存
  free(st1);
  st1 = NULL;
  return 0;
}

由上面的分析我们可以看到,我们完全可以在结构体里面创建一个整形指针(其他类型也可以),然后对其进行动态内存开辟就可以完全替代柔性数组的功能,因此柔性数组这一功能并不常用,我们仅作了解即可

目录
打赏
0
0
0
0
4
分享
相关文章
pymalloc 和系统的 malloc 有什么区别?
pymalloc 和系统的 malloc 有什么区别?
C中的 malloc 和C++中的 new 有什么区别
在C语言中,`malloc`函数用于在运行时分配内存,返回指向所分配内存的指针,需显式包含头文件 `&lt;stdlib.h&gt;`。而在C++中,`new`不仅分配内存,还对其进行构造初始化,且直接使用类型声明即可,无需额外包含头文件。`new`还支持数组初始化,能更好地融入C++的面向对象特性,而`malloc`仅作为内存分配工具。使用完毕后,`free`和`delete`分别用于释放`malloc`和`new`分配的内存。
100 21
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
1033 1
|
6月前
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
115 0
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
145 23
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
24 1
一文彻底搞清楚C语言的函数
|
3月前
|
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
137 15
|
3月前
|
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
75 24