C语言动态内存开辟
一、内存分配
1.1 内存分区管理 内存分成三部分,栈空间、堆空间和静态区。栈空间较小,速度较快,符合栈先进后出的特点,由系统自动管理空间;堆内存空间较大,由人员手动开辟和释放,返回空间地址给栈区指针变量管理,堆内存符合树形结构;静态区存放全局变量和用static修饰的变量。 1.2 变量在内存的分配 栈区内存:存放局部变量和参数、函数栈帧、函数调用完之后,释放栈帧和函数中的局部变量。如果调用函数过多,开辟栈帧过多,可能出现栈溢出的问题,如错误的递归调用。栈区内存管理由系统自动完成。 堆区内存:堆区空间远大于栈区空间,要使用堆空间,需由人员手动开辟,并在使用完后手动释放内存空间。开辟使用方式为:用malloc、calloc、realloc函数开辟空间并返回空间的起始地址给栈区的指针变量,用栈区的指针变量管理这部分空间,用free函数释放堆空间并将栈区的指针变量置为NULL。 静态区:存放全局变量和static修饰的变量。整个工程可见,全局变量在main函数调用之前就已开辟栈空间。 1.3 动态开辟内存的函数 动态开辟相关函数在头文件<stdlib.h>中。 malloc:void * malloc(size_t sz); malloc函数接收一个无符号整型数值作为开辟堆空间的大小,单位为字节,返回空间的起始地址,当栈区的指针变量接收时要做指针类型转换。 如:int * p = (int *)malloc(40);//开辟40个字节大小的堆空间,起始地址赋值给指针变量p。 calloc:void * calloc(size_t num, size_t sz); calloc函数接收两个参数,第一个表示元素个数,第二个表示元素所含字节数,也就是说calloc函数开辟的空间大小为(num * sz)个字节。与malloc不同的是,calloc开辟的空间初始值都赋值为0,其他与malloc无异。 realloc:void * realloc(void * addr, size_t newsz); realloc函数用于自动扩容,重新分配内存。参数addr是原来开辟的旧地址,newsz是要开辟的新空间的大小。注意realloc返回值有两种情况,第一种是扩容空间足够,在原来旧地址的基础上在后面加上新空间,此时返回值还是原来的旧地址。当扩容的空间不够时,会重新开辟一块空间,并将原来空间内的内容拷贝到新空间,返回新空间的地址,原来的空间释放,如下图描述。 free():释放指针变量指向的堆内存空间,与开辟空间函数配合使用。
二、结合题目分析
1.1 题目描述 代码如下所示,分析出现的结果。
void getMemory(char * p)
{
p = (char *)malloc(100);
}
void test(void)
{
char * str = NULL;
getMemory(str);
strcpy(str, "hello world!");
printf("%s\n", str);
}
int main()
{
test();
return 0;
}
1.2 题目分析 首先在main中调用test函数: (1)在栈中创建一个指针变量str,设置为NULL,test中调用getMemory(),将str的值传递给形参p,p=NULL。 (2)在getMemory函数中,开辟100个字节的堆内存,并把起始地址赋给p。此时p中存的便是堆内存空间的地址。 (3)当getMemory函数调用完毕,栈帧释放,局部变量p被回收,而此时test中的str还是NULL,没有指向任何空间,堆空间中开辟的100个字节便成了脱缰野马,无法管理,发生内存泄漏。
1.3 题目改错 既然是因为失去缰绳,无法控制,那就设法将缰绳再次掌握起来,p是一个临时变量,函数调用完就释放,那么就设法让test内的str也能掌控开辟的空间。要改变str的值,那么可以传递str的地址给getMemory,有了地址,就可以改变str里面的内容。使用二级指针。
void getMemory(char ** p)
{
*p = (char *)malloc(100);
}
void test(void)
{
char * str = NULL;
char ** pstr = &str;
getMemory(pstr);
strcpy(str, "hello world!");
printf("%s\n", str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
(1)将str这个指针变量的地址传递给p,pstr内是str的地址。 char ** p = pstr; (2)*p = (char *)malloc(100); // 等价于 str=(char *)malloc(100); 将str的内容赋值为开辟的堆空间的地址。这样,当getMemory调用完毕,释放空间之后,str中的地址指向开辟的空间,这样野马就相当于有了缰绳,便不再是野马,可以管理。 (3)当使用完之后,要及时使用free释放空间,并置str=NULL; free(str); str = NULL; (4)test函数调用完释放栈帧,main结束释放栈帧。
三、题目总结
- 题目中错误的方式是将指针变量的值传递给另一个函数的参数,参数改变只会发生在调用的函数内,原函数的变量值不发生改变,所以应该用地址传递,这样调用函数就可以改变原函数内指针变量str的值。
- 内存主要分为栈内存、堆内存以及静态区(自己的理解)。
栈内存有用于分配局部变量空间和函数栈帧,参数压栈等功能。并且由cpu自动管理回收空间,无需人员手动释放,栈内存空间较小,速度较快,符合栈先进后出的特点; 堆内存是需人员手动开辟的内存空间,空间比栈区大很多,用于开辟存储较大的对象,并返回开辟空间的引用(地址)给栈区的指针变量管理。用于开辟堆内存空间的常用函数有malloc、calloc、realloc、free四个,开辟空间并使用完之后一定要free()释放堆空间,并置栈区的引用指针为NULL,防止非法访问内存(在Java、Python中有垃圾回收机制自动回收闲置的堆空间); 静态区存放全局变量和用static修饰的变量,全局变量在main函数之前就已经开辟空间存储。
|