一.为什么要有动态内存分配
通过前面的学习我们已经掌握了使用变量和数组来进行内存的开辟。
上面所说的这两种内存的开辟方式有两个特点:
- 空间开辟的大小是固定的。
- 数组在生命的时候,必须指定数组的长度,数组空间一旦确定了大小就不能再调整。
但是对于空间的的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才知道,那数组编译时开辟空间的方式就不能满足了。
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:忘记释放不再使用的动态内存开辟的空间会造成内存泄漏。
切记,动态内存开辟的空间一定要正确释放!!!
对动态内存的越界访问是一种编程错误,它可以导致程序的不可预测行为和安全漏洞。以下是几个原因:
内存越界访问可能会覆盖其他变量或数据,导致数据错误或损坏。这样的错误很难调试和修复。
越界访问可能会导致程序崩溃或产生异常。这会影响程序的稳定性和可靠性。
越界访问可能会导致程序的安全漏洞,如缓冲区溢出。攻击者可以利用这些漏洞来执行恶意代码或篡改数据。
越界访问会违反编程语言的内存管理规则。动态内存分配和释放是由编程人员负责管理的,如果出现越界访问,可能导致内存泄漏或资源浪费。
因此,编程时应该避免对动态内存进行越界访问,以确保程序的正确性、稳定性和安全性。
补充:
动态内存创建二维数组的方法如下:
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; }
柔性数组的优势:
柔性数组是一种特殊的数据结构,它允许在数组的最后一个元素之后继续添加新的元素,而不需要修改数组的大小。柔性数组的优势主要体现在以下几个方面:
节省空间:由于柔性数组的大小可以根据需要动态增长,因此可以节省内存空间。在使用传统的固定大小数组时,如果数组大小预设过大,就会浪费大量的内存空间;如果数组大小预设过小,可能会导致数组溢出。柔性数组可以根据实际需要动态分配内存,避免了这个问题。
简化操作:由于柔性数组可以在尾部追加新的元素,而不需要修改原有的元素位置,因此添加或删除元素的操作相对简单。而在传统的固定大小数组中,插入或删除元素需要移动其他元素,操作复杂度较高。
提高效率:柔性数组的动态分配内存操作相对高效,因为内存可以在堆上连续分配,无需重复开辟新的存储空间。而传统的固定大小数组,如果需要扩展大小,可能需要重新分配一块更大的内存空间,并将原有的元素复制到新的空间中,效率较低。
以下是一个使用柔性数组的代码示例:
#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)是一种在内存中动态分配空间的数据结构,可以根据需要调整数组的大小。它的劣势包括以下几个方面:
内存浪费:由于柔性数组的大小是在运行时确定的,因此需要分配一定的额外空间来存储未使用的部分。这样会导致内存的浪费,在大规模应用中可能会成为一个问题。
不支持直接访问:由于柔性数组的大小是动态变化的,因此无法通过下标直接访问某个元素,而需要使用指针来进行访问。这增加了代码的复杂度,并且容易引入指针相关的错误。
可能导致内存碎片化:由于柔性数组的大小是动态变化的,当数组大小减小时,可能会导致内存产生碎片。这会影响内存的利用率,可能导致系统的性能下降。
代码示例:
#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
函数释放内存,以避免内存泄漏。