本文主要介绍了C语言中常用的内存函数和数组的动态内存分配,并稍微提了一嘴柔性数组。动态内存分配是C语言中十分重要的一环,其中对二维数组的动态内存分配是个难点,需要多思考。
要开辟一个数组,我们可以很简单做到:
int arr[10];
char str[20];
struct S s[10];
我们可以很容易地声明整型数组、字符数组、结构体数组…
但是,这样声明出来的数组有很明显的缺点:
但实际上,很多情况下我们并不知道自己要开辟多大的空间,这些很多时候是只有在程序跑起来才知道的,所以这就引出了动态内存分配。
动态内存分配出来的空间是在堆区开辟的,这是它与通过定义数组分配出来的空间最本质的区别。
动态内存分配是需要调用动态内存函数实现的,下面介绍四种内存函数,点击超链接即可转到官方解释。
它会向内存申请一块连续可用的空间,空间大小是 size 个字节,并返回指向这块空间的指针。
它返回的是个指针,所以在使用它时要用指针接收:
char* ch = malloc(sizeof(char) * size1);
int* arr = malloc(sizeof(int) * size2);
struct S* s = malloc(sizeof(stuctt S) * size3);
但这样并不严谨,因为 malloc 返回类型是空指针,所以在接受它的返回值时最好再对它进行强制类型转换:
char* ch = (char*)malloc(sizeof(char) * size1);
int* arr = (int*)malloc(sizeof(int) * size2);
struct S* s = (struct S*)malloc(sizeof(stuctt S) * size3);
但是,当动态内存开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查:
char* ch = (char*)malloc(sizeof(char) * size);
if(ch == NULL)
{
perror("fun_name");
return;
}
它的参数是一个指针,指针指向的空间是动态内存分配出来的一块空间。它会释放掉这块空间,将这块空间还给操作系统。
它释放的空间一定是要动态内存分配出来的,这块空间一定是在堆区的,否则会引发异常:
用 free 去释放栈区的空间,这是万万不能够的!!
此外,如果传过去的指针是个 NULL 空指针,free 就会纯纯摆烂,啥也不干。
释放空间之后,原指针就没有任何意义,但 free 不会自动给它置成空指针,此时他就成为一个野指针,所以我们要即及时将其置成空指针:
int* ptr = (int*)malloc(sizeof(int) * size);
if(ptr == NULL)
{
perror("fun_name");
return;
}
...
free(ptr);
ptr = NULL;
num :元素个数
size :每个元素的大小(byte)
所以它的作用就是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0。
所以它的使用和 malloc 基本一致:
int *p = (int*)calloc(10, sizeof(int));
if(NULL == p)
{
perror("fun_name");
return;
}
...
free(p);
p = NULL;
虽然我们能通过 malloc 或 calloc 函数动态内存分配一块空间,但这块空间分配完成后大小也是固定的,如果空间满了需要扩容或空间多了需要缩减,这时 realloc 函数就登场了。
realloc 的作用是对已经动态分配的一块空间再次分配。
它有两个参数:
- memblock:要调整的内存地址,这块内存是动态内存分配得到的
- size:以字节为单位的新大小
它会返回调整之后的内存起始位置。
关于调整之后的内存起始位置会出现以下两种情况:
- 与原来内存的起始位置相同
- 与原来内存的起始位置不同
- 当要缩小原有的内存时,原来的内存空间已经足够,此时它的返回值就是原来内存的起始位置。
- 当要扩大原有的内存时,又有两种情况:
原有空间之后有足够大的内存时,直接在原内存的基础上再开辟后边几个连续的空间,此时它的返回值是原来内存的起始位置;
原有空间之后没有足够大的内存进行扩容时,此时会在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,与原来内存的起始位置不同。这时会拷贝原来空间的内容到新的空间的相应位置,原空间就被释放掉了。
所以,在使用 realloc 函数时就要注意用一个临时指针接收,当内存调整成功后再将临时指针赋给原指针:
int main()
{
int* ptr = (int*)malloc(100);
if (ptr == NULL)
{
preeor("main");
return;
}
...
//定义一个临时指针接收新地址
int* tmp = NULL;
tmp = (int*)realloc(ptr, 1000);
if (tmp != NULL)
{
ptr = tmp;
}
...
free(ptr);
ptr = NULL;
return 0;
}
对于动态内存分配我们主要用于动态开辟一维和二维数组。
下面就看看开辟数组的方法和正确使用这块空间并释放。
动态开辟一维数组还是比较简单的:
int main()
{
int size = 10;
//给整形数组动态分配
int* arr = (int*)malloc(sizeof(int) * size);
if (arr == NULL)
{
perror("main");
return;
}
//给数组赋值
for (int i = 0; i < size; i++)
arr[i] = i;
free(arr);
arr = NULL;
return 0;
}
在上面这段代码中,对动态内存分配的一块空间我们直接用给数组赋值的方式对其赋值,其可行性是源于数组的指针式访问和下标式访问:
*(p + i) <==> p[i]
*(*(arr + i ) + j) <==> arr[i][j]
不过一定要记得使用完这块空间要及时释放。
对于二维数组的动态开辟,其方法是不唯一的,下面给出三种方法。
第一种
由于二维数组在内存中是连续存放的,所以我们可以开辟一大块空间将二维数组当成一维数组存放起来。这样数组元素的存储在内存中是连续的。
第二种
动态开辟一个二维数组,可以开辟一个指针数组,每个元素存放的是一个指针,每个指针都指向一个数组,再分别对每个一维数组分配空间。
但是,这种方式开辟出来的数组不是连续存储的
:
第三种
动态开辟一个二维数组,还可以借用数组指针,然后通过对数组指针访问到数组元素,此时开辟出来的数组是连续存储的
。其实这种方法和第一种方法有同工异曲之妙,只是这种方式访问数组元素更简单:
下面是每种方法的代码实现:
//方法1
#define ROW 4
#define COL 4
int main()
{
//动态开辟
int* arr = (int*)malloc(sizeof(int) * ROW * COL);
if (arr == NULL)
{
perror("main");
return;
}
//访问数组并赋值
int count = 1;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
arr[i * COL + j] = count++;
//释放内存
free(arr);
arr = NULL;
return 0;
}
//方法2
#define ROW 4
#define COL 4
int main()
{
//用二级指针动态申请二维数组
int** arr = (int**)malloc(sizeof(int*) * ROW);
//这样只开辟了ROW个存放整型指针的空间
if (arr == NULL)
{
perror("main");
return;
}
//对每个一维数组开辟空间
for (int i = 0; i < ROW; i++)
{
arr[i] = (int*)malloc(sizeof(int) * COL);
//给每个一级指针arr[i]分配COL个整型空间
if (arr[i] == NULL)
{
perror("main");
return;
}
}
//访问数组元素并赋值
int count = 1;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
arr[i][j] = count++;
//释放二维数组的每个一维数组
for (int i = 0; i < ROW; i++)
{
free(arr[i]);
arr[i] = NULL;
}
//释放二级指针申请的数组
free(arr);
arr = NULL;
return 0;
}
//方法3
#define ROW 4
#define COL 4
int main()
{
//用数组指针形式申请一个ROW行COL列的二维数组
int(*arr)[COL] = (int(*)[COL])malloc(sizeof(int) * ROW * COL);
if (arr == NULL)
{
perror("main");
return;
}
//访问数组成员并对其赋值
int count = 1;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
arr[i][j] = count++;
//释放内存
free(arr);
arr = NULL;
return 0;
}
无论是哪种方式,刚用起来肯定生疏,但用多了就熟悉了。
而且,一定要记得释放内存,特别是第二种方式!
柔性数组(flexible array)是一种不完整类型,而 C99 的标准,就支持了这种类型。
标准规定:结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
typedef struct S
{
int i;
int a[0];//柔性数组成员
}s;
对于有些编译器可能报错,a[0] 改成 a[ ] 即可。
柔性数组在结构中声明,柔性数组成员前面必须有至少一个其他成员。
包含柔性数组成员的结构用 malloc() 函数进行内存的动态分配,并且分配的内存应该大于结构的大小
,以适应柔性数组的预期大小。
//对结构体进行动态内存分配
s* p = (s*)malloc(sizeof(s) + 100 * sizeof(int));
if (p == NULL)
{
perror("main");
return;
}
...
free(p);
p = NULL;