【C语言——动态内存管理】

一.为什么要有动态内存分配

通过前面的学习我们已经掌握了使用变量和数组来进行内存的开辟

上面所说的这两种内存的开辟方式有两个特点:

  • 空间开辟的大小是固定的。
  • 数组在生命的时候,必须指定数组的长度,数组空间一旦确定了大小就不能再调整。

但是对于空间的的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才知道,那数组编译时开辟空间的方式就不能满足了。

C语言中引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

二.malloc和free

1.malloc

void *malloc(size_t size)

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要检查。
  • 返回值的类型是void*,所以malloc函数并不知道开辟的空间类型,具体在使用的时候使用者自己来决定。
  • 如果参数size_t 为0,malloc的行为标准是未定义的,取决于编译器。

 2.free

该函数是专门用来做动态内存的释放和回收的。

(用来释放动态开辟的内存)

void  free(void*ptr)

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果ptr的参数是NULL指针,则函数什么事都不做。

注:malloc和free函数都在stdlib.h这个头文件中。

eg:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int num = 0;
	scanf("%d", num);
	int arr[num] = { 0 };
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (ptr != NULL)
	{
		for (int i = 0; i < num; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//该步骤是有必要的
	return 0;
}

三.calloc和realloc

1.calloc

也是用来动态内存分配的。

void *calloc(size_t num,size_t size)

  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.
  • 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为权0.

eg:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p != NULL)
	{
		for (int i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);
	p = NULL;
	return 0;
}

所以我们如果要对申请的内存空间的内容初始化,那么可以考虑calloc函数。

2. realloc(涉及动态内存增容)

  • 该函数的出现让动态内存管理更加灵活。
  • 有时候我们会发现过去申请的空间太小了,有时候会觉得过去申请的空间太大了,那么为了合理的使用内存,我们一定会对内存的大小做灵活的调整。

void *realloc(void* ptr,size_t size)

  • ptr是函数的内存地址。
  • 返回值为调整后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • realloc在调整内存空间的是存在两种情况:

(1.)原有的空间之后有足够大的空间。

此时要扩展内存就直接在原有的内存之后追加空间,原来的空间的数据不发生变化。

(2.)原有的空间之后没有足够大的空间。

此时的空间扩展方法是:在堆空间上另找一个合适大小的的连续空间来使用。这样函数返回的是一个新的地址。

eg:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr == NULL)
	{
		//业务处理
	}
	else
	{
		return 1;
	}
	//扩展容量
	ptr = (int*)realloc(ptr, 1000);//这样写的不适合
	int* p = NULL;//应将realloc的函数的函数返回值放在p中,不为空再放到ptr中
	p = (int*)realloc(ptr, 1000);
	if (p != NULL)
	{
		ptr = p;
	}
	//业务处理
	free(ptr);
	ptr = NULL;
	return 0;
}

PS:忘记释放不再使用的动态内存开辟的空间会造成内存泄漏。

切记,动态内存开辟的空间一定要正确释放!!!

对动态内存的越界访问是一种编程错误,它可以导致程序的不可预测行为和安全漏洞。以下是几个原因:

  1. 内存越界访问可能会覆盖其他变量或数据,导致数据错误或损坏。这样的错误很难调试和修复。

  2. 越界访问可能会导致程序崩溃或产生异常。这会影响程序的稳定性和可靠性。

  3. 越界访问可能会导致程序的安全漏洞,如缓冲区溢出。攻击者可以利用这些漏洞来执行恶意代码或篡改数据。

  4. 越界访问会违反编程语言的内存管理规则。动态内存分配和释放是由编程人员负责管理的,如果出现越界访问,可能导致内存泄漏或资源浪费。

因此,编程时应该避免对动态内存进行越界访问,以确保程序的正确性、稳定性和安全性。

补充:

动态内存创建二维数组的方法如下:

int** createArray(int rows, int cols) {
    int** arr = new int*[rows]; // 创建行指针数组
    for (int i = 0; i < rows; i++) {
        arr[i] = new int[cols]; // 创建每一行的列数组
    }
    return arr;
}

int main() {
    int rows = 3;
    int cols = 4;
    int** arr = createArray(rows, cols);

    // 使用 arr 访问二维数组元素
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = i * cols + j; // 赋值
            cout << arr[i][j] << " "; // 输出
        }
        cout << endl;
    }

    // 释放动态内存
    for (int i = 0; i < rows; i++) {
        delete[] arr[i]; // 释放每一行的列数组
    }
    delete[] arr; // 释放行指针数组

    return 0;
}

在这个例子中,createArray 函数接受行数和列数作为参数,创建一个二维数组,并返回指向该数组的指针。然后在 main 函数中,我们使用这个函数创建一个 3 行 4 列的二维数组,并对其进行赋值和输出操作。最后,我们需要手动释放动态分配的内存。

四.柔性数组

柔性数组是一种特殊的数据结构,它允许在数组的末尾动态添加元素。柔性数组通常用于解决数组长度不确定的问题。

在传统的数组中,数组的大小在创建时就需要确定,并且不能动态改变。但是柔性数组允许在创建数组时只指定部分空间,然后在需要的时候动态添加更多的元素。

柔性数组的实现原理是在数组的末尾预留一定的空间,用于存储新添加的元素。当需要添加元素时,可以通过重新分配内存的方式来扩展数组的长度,并将新的元素添加到预留的空间中。

柔性数组的优点是可以节省内存空间,因为它只分配实际需要的空间。而传统的数组在创建时需要分配固定大小的空间,可能造成内存的浪费。

柔性数组在实际应用中常用于动态增长的数据结构,如动态数组、链表等。它能够灵活地适应数据的变化,提供高效的内存管理。

C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

例如:

struct st_type
{
	int i;
	int a[0];//柔性数组成员
};

struct st_type
{
	int i;
	int a[];//柔性数组成员
};

柔性数组的特点:

  • 结构中的柔性数组成员前面必须有至少有有一个其他成员 。
  • sizeof返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构体用malloc()函数进行内存的动态内存分配,并且分配到的内存应该大于结构的大小,以适应柔性数组的预期大小。

例如:

#include <stdio.h>
typedef struct st_type
{
	int i;
	int a[0];
}type_a;
int main()
{
	printf("%d ", sizeof(type_a));//输出的结果为4
}

柔性数组的使用:

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

// 定义包含柔性数组的结构体
struct FlexArray {
  int length;
  int data[]; // 柔性数组
};

int main() {
  int size = 5;
  
  // 分配内存给结构体
  struct FlexArray* flexArray = malloc(sizeof(struct FlexArray) + size * sizeof(int));

  if (flexArray == NULL) {
    printf("Failed to allocate memory.\n");
    return 1;
  }

  flexArray->length = size;

  // 给柔性数组赋值
  for (int i = 0; i < size; i++) {
    flexArray->data[i] = i * 2;
  }

  // 输出柔性数组的值
  for (int i = 0; i < size; i++) {
    printf("%d ", flexArray->data[i]);
  }
  
  // 释放内存
  free(flexArray);

  return 0;
}
 

​

柔性数组的优势:

柔性数组是一种特殊的数据结构,它允许在数组的最后一个元素之后继续添加新的元素,而不需要修改数组的大小。柔性数组的优势主要体现在以下几个方面:

  1. 节省空间:由于柔性数组的大小可以根据需要动态增长,因此可以节省内存空间。在使用传统的固定大小数组时,如果数组大小预设过大,就会浪费大量的内存空间;如果数组大小预设过小,可能会导致数组溢出。柔性数组可以根据实际需要动态分配内存,避免了这个问题。

  2. 简化操作:由于柔性数组可以在尾部追加新的元素,而不需要修改原有的元素位置,因此添加或删除元素的操作相对简单。而在传统的固定大小数组中,插入或删除元素需要移动其他元素,操作复杂度较高。

  3. 提高效率:柔性数组的动态分配内存操作相对高效,因为内存可以在堆上连续分配,无需重复开辟新的存储空间。而传统的固定大小数组,如果需要扩展大小,可能需要重新分配一块更大的内存空间,并将原有的元素复制到新的空间中,效率较低。

以下是一个使用柔性数组的代码示例:

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

typedef struct {
    int size; // 数组当前大小
    int capacity; // 数组总容量
    int* array; // 指向柔性数组的指针
} FlexArray;

FlexArray* createFlexArray(int capacity) {
    FlexArray* flexArray = malloc(sizeof(FlexArray));
    flexArray->size = 0;
    flexArray->capacity = capacity;
    flexArray->array = malloc(capacity * sizeof(int));
    return flexArray;
}

void append(FlexArray* flexArray, int value) {
    if (flexArray->size == flexArray->capacity) {
        // 若数组容量已满,重新分配内存空间
        flexArray->capacity *= 2;
        int* newArray = realloc(flexArray->array, flexArray->capacity * sizeof(int));
        if (newArray == NULL) {
            printf("内存分配失败!\n");
            return;
        }
        flexArray->array = newArray;
    }
    flexArray->array[flexArray->size++] = value;
}

void printArray(FlexArray* flexArray) {
    for (int i = 0; i < flexArray->size; i++) {
        printf("%d ", flexArray->array[i]);
    }
    printf("\n");
}

int main() {
    FlexArray* flexArray = createFlexArray(5);
    append(flexArray, 1);
    append(flexArray, 2);
    append(flexArray, 3);
    append(flexArray, 4);
    append(flexArray, 5);
    append(flexArray, 6);
    append(flexArray, 7);
    append(flexArray, 8);
    printArray(flexArray);
    free(flexArray->array);
    free(flexArray);
    return 0;
}

此代码示例展示了如何使用柔性数组来创建一个动态增长的数组。首先创建了一个 FlexArray 结构体,并初始化了数组的大小和容量,然后通过 createFlexArray 函数分配内存空间。append 函数用于向柔性数组追加新的元素,当数组容量不足时会动态分配更多的内存空间。printArray 函数用于打印数组中的所有元素。在 main 函数中,通过调用这些函数来演示柔性数组的使用。最后,释放内存空间,避免内存泄漏。

柔性数组的劣势: 

柔性数组(Flexible Array)是一种在内存中动态分配空间的数据结构,可以根据需要调整数组的大小。它的劣势包括以下几个方面:

  1. 内存浪费:由于柔性数组的大小是在运行时确定的,因此需要分配一定的额外空间来存储未使用的部分。这样会导致内存的浪费,在大规模应用中可能会成为一个问题。

  2. 不支持直接访问:由于柔性数组的大小是动态变化的,因此无法通过下标直接访问某个元素,而需要使用指针来进行访问。这增加了代码的复杂度,并且容易引入指针相关的错误。

  3. 可能导致内存碎片化:由于柔性数组的大小是动态变化的,当数组大小减小时,可能会导致内存产生碎片。这会影响内存的利用率,可能导致系统的性能下降。

代码示例:

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

struct FlexArray {
    int length;
    int arr[];  // 柔性数组
};

int main() {
    int size = 5;
    struct FlexArray* flexArray = malloc(sizeof(struct FlexArray) + sizeof(int) * size);

    flexArray->length = size;
    for (int i = 0; i < flexArray->length; i++) {
        flexArray->arr[i] = i;
    }

    for (int i = 0; i < flexArray->length; i++) {
        printf("%d ", flexArray->arr[i]);
    }

    free(flexArray);
    return 0;
}

在上述代码中,我们定义了一个包含柔性数组的结构体 FlexArray。使用 malloc 函数分配内存时,我们通过 sizeof 运算符计算了结构体的大小,并加上了柔性数组的大小。然后,我们可以通过指针访问柔性数组,并使用它存储数据。最后,务必记得使用 free 函数释放内存,以避免内存泄漏。

关注博主,优质内容不断更新!!!

如有错误,还望指出! 

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-04-25 10:12:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-25 10:12:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-25 10:12:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-25 10:12:03       18 阅读

热门阅读

  1. Edge的使用心得与深度探索

    2024-04-25 10:12:03       16 阅读
  2. tomcat到底是干嘛的?

    2024-04-25 10:12:03       14 阅读
  3. SpringBoot集成JPA及基本使用

    2024-04-25 10:12:03       18 阅读
  4. 直接扩频通信系统的Matlab实现

    2024-04-25 10:12:03       14 阅读
  5. pandas数据分析综合练习50题 - 地区房价分析

    2024-04-25 10:12:03       50 阅读
  6. NLP(7)--Embedding、池化、丢弃层

    2024-04-25 10:12:03       23 阅读
  7. 检查现有的remote repo 并换新的remote repo

    2024-04-25 10:12:03       31 阅读
  8. npm详解:Node.js的包管理器

    2024-04-25 10:12:03       17 阅读
  9. Linux 解压报错

    2024-04-25 10:12:03       12 阅读