1、引入:
一般开辟内存空间的方法:
int g_val = 0; //在栈空间上开辟4个byte。
int array [10] = {0}; //在栈空间上开辟40个byte的连续空间。
或者int array [] = {0,1};//在栈空间上开辟8个字节的连续空间。
但是上述开辟空间的方式有两个特点:
1、空间开辟大小是固定的。
2、数组在申明的时候,必须指定数组的长度,或是初始化所需要的元素,它所需要的内存在编译期间分配。
这就意味着我们需要在创建的时候就知道我们需要多少的空间,但是有时候我们所需的空间大小往往是在编写的过程中才知道,或者说需要调整,但是上述的方法显然是不能满足我们的需求的,此时就需要动态内存分配:
首先要了解内存的布局:
内存大致被分为这么几个区域,其中内核空间是用来留给 *** 作系统以及关键组件的,是用户代码不能读写的,栈区主要用来存放局部变量以及函数形参,堆区主要用来动态内存分配,所谓的数据段也就是静态区,主要用来存放static修饰的静态变量和创建的全局变量。
所以,动态内存是在堆区开辟的。
2、动态内存开辟函数介绍:
<1> malloc和free
mallo是一个动态内存开辟的函数:
其中参数size是你希望开辟的字节数。
这个函数向内存申请一块连续的空间,并返回指向这块空间的指针。
(malloc使用的库是stdlib.h)
(1)如果开辟成功,则返回一个指向所开辟空间的指针。
(这块空间的首地址)
(2)如果开辟失败,就返回NULL,因此malloc函数的返回值一定要检查。
(3)返回值的类型是void*,所以malloc函数并不知道开辟的空间的类型,具体使用时由使用者自己决定。
(4)如果参数size传入为0,malloc的行为是标准未定义的,取决于编译器。
详细使用样例请看代码:
#include
#include #include int main() { int i = 0; int* p = (int*)malloc(sizeof(int) * 5); if (p != NULL) { for (i = 0; i < 5; i++) { *(p + i) = i; } for (i = 0; i < 5; i++) { printf("%d ", *(p + i)); } } else { printf("%s\n", strerror(errno)); } return 0; }
#include
#include #include #include int main() { int i = 0; int* p = (int*)malloc(INT_MAX); if (p != NULL) { for (i = 0; i < 5; i++) { *(p + i) = i; } for (i = 0; i < 5; i++) { printf("%d ", *(p + i)); } } else { printf("%s\n", strerror(errno)); } return 0; } C语言提供了另外一个函数 free 专门用来做动态内存的释放和回收的。
和栈区使用不同,栈区空间使用后是由 *** 作系统自动回收的。
例如一个函数return后,为这个函数在栈区开辟的空间就被 *** 作系统自动回收了(函数栈帧销毁)。
而堆区的申请的内存则是整个程序结束后才被 *** 作系统回收,所以,当我们使用完堆区申请的内存后有,需要用 free 释放来将这块空间还给 *** 作系统。
free函数是用来释放动态开辟的内存的.
如果参数 memblock不是动态开辟的,那么free函数的行为是未定义的。
如果参数 memblock为NULL,那么free函数什么也不做。
请看下面的例子:
#include
#include #include int main() { int i = 0; int* p = (int*)malloc(5 * sizeof(int));//开辟动态内存空间 if (p != NULL) { //使用 for (i = 0; i < 5; i++) { *(p + i) = i; } for (i = 0; i < 5; i++) { printf("%d ", *(p + i)); } } else { printf("%s\n", strerror(errno)); } //释放 free(p); p = NULL; return 0; } 使用完开辟的堆区空间后要进行释放,然后将指向这块空间的指针置为NULL。
置为NULL的原因:
free函数只是将这块空间还给 *** 作系统,并不会把指向这块空间的指针置为NULL,这就意味着在free之后,该指针就是一个野指针,是不能进行访问的。
所以在free之后要置为NULL。
以免造成野指针的访问。
<2> calloc
C语言还提供了一个进行动态内存开辟的函数 calloc 。
参数size是希望开辟的单位的大小,参数num是所需开辟的单位的个数,使用规则和malloc函数是一样的。
例子如下:
#include
#include
#include
int main()
{
int i = 0;
int* p = (int*)calloc(5, sizeof(int));//开辟动态内存空间
if (p != NULL)
{
//使用
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
}
else
{
printf("%s\n", strerror(errno));
}
//释放
free(p);
p = NULL;
return 0;
}
malloc 和 calloc 的区别:
malloc函数在开辟空间时,只是给了一块空间的访问权限,但并未对所申请区域的数据进行初始化,而calloc函数在开辟时,会将所申请区域的数据全部初始化为0。
(未初始化时内存空间内存储的是随机值)
如果我们在申请动态内存空间时,需要对所申请的空间初始化,那么可以使用calloc函数。
<3> realloc
realloc函数的出现让动态内存管理更加灵活。
realloc函数可以对动态开辟的内存大小进行调整。
memblock参数是需要调整的动态内存空间的指针,size参数是所需调整到的字节数。
返回值是调整之后的空间的首地址。
注:realloc函数在调整空间时,会有两种情况:
1、原有空间之后有足够大的空间
那么就会在原有空间后面直接追加空间,原来空间的数据不发生改变。 返回的指针指向的空间仍然是realloc之前的空间,不过是扩容了。
2、原有空间之后没有足够大的空间
扩展的方法是:在堆空间上另外找一块合适大小的连续空间来使用,原有的数据会被挪到新开辟的空间中,返回的指针指向的空间也是新的。 原来的那块空间就会被回收。
如果扩容失败,会返回NULL 。
所以在接收realloc返回的指针时,要创建一个中间变量,用来判断是否开辟成功,再决定是否将新开辟的空间交给原来的指针保管。 若是直接将返回值交给缘来的指针,若开辟失败,原指针就被置为NULL,原数据也就不能访问了
使用例子:
#include
#include
#include
int main()
{
int i = 0;
int* p = (int*)calloc(5, sizeof(int));//开辟动态内存空间
if (p != NULL)
{
//使用
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
}
else
{
printf("%s\n", strerror(errno));
}
printf("\n");
//调整
int* ptr = (int*)realloc(p, sizeof(int) * 10);
if (ptr != NULL)
{
//使用
p = ptr;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
else
{
printf("扩容失败\n");
}
//释放
free(p);
p = NULL;
return 0;
}
其中ptr就是一个中间变量。
特殊情况:
若给relloc传入空指针,即realloc(NULL, size);此时功能类似malloc 。
水平有限,欢迎指正。
(下一篇就写写使用这些函数容易出错的点吧)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)