目录
本章重点
1:malloc,calloc,realloc,free 函数
2:常见动态内存错误
3:几道经典的笔试题
首先我们平时所创建的变量和数组都是在栈区上开辟的内存,空间开辟的大小是固定的。
而有些程序是要在运行的时候它所需要的空间才能知道。
这4个函数在内存中使用的时候是在堆区开辟空间的。
malloc:使用语法是malloc(size_byte);,size_byte指的是需要开辟多少字节空间的内存,malloc函数的返回值是开辟空间的地址类型为void*,所以在使用的时候我们需要根据需求来强制类型转换。
malloc开辟空间的时候可能不会成功的开辟,如果失败的话则返回NULL,所以一般在使用完malloc开辟的空间之后我们会对地址来进行判断,这样可以及时提醒我们是否开辟空间成功。
malloc对于开辟的空间的内容不会初始化,且最后我们需要借助free函数来释放我们所开辟的空间。对于malloc(0)这个行为是未定义的。
free:使用语法是free(ptr),这里的ptr是一个指针,返回值为void,函数参数为void*的指针,它是用来释放动态内存开辟的空间,对于非动态内存开辟的空间如果用free来释放,那么程序可能会直接报错,如果对与NULL指针释放,及free(NULL),这时我们的free啥都不会做。
calloc:使用语法为calloc(num,size),num表示要开辟多少个数量,size表示每个空间的大小,单位是字节,函数功能是开辟num个大小为size的空间,开辟成功返回值为开辟空间的地址,开辟失败返回值为NULL,与malloc最主要的区别就是它开辟完空间之后会将每个字节的空间大小全部初始化为0如下图:>
所有当我们到底使用malloc还是calloc我们可以看我们是否需要初始化空间。
realloc:它是用来调整我们动态内存开辟空间大小的,有时我们开辟的空间过小,也有时我们开辟的空间过大,我们都可以用realloc来调整我们动态内存空间的大小,所有realloc函数的出现使我们
更加合理的来动态内存管理。
realloc函数的原型
void* realloc (void* ptr, size_t size);
ptr:代表我们所需要调整空间的地址
size:表示我们需要重新调整空间的大小,单位是字节
当realloc需要扩容的时候,会出现两种情况:>
情况1:当realloc所开辟的空间的大小在原空间后面有足够大的空间,那么realloc的返回值还是需要调整空间的地址。
情况2:原空间后面没有多余的空间,则在这时我们的realloc函数会在堆区的寻找另外一块足够大的空间去申请一块空间,这时realloc会自动将原空间的内容拷贝到新开辟的空间处,并且realloc函数会自动释放原来地址的空间,然后返回新的空间的起始地址。
常见错误1:对NULL解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;
free(p);
}
int main()
{
test();
return 0;
}
解释:因为INT_MAX,是整形取值的最大值,而用malloc去开辟空间的时候可能会失败,所以p可能是NULL,此时*p则代表对空指针进行解引用,此时属于非法访问内存了。
解决办法:对于刚开辟出来的空间我们就对其进行判断,并且如果申请空间失败就直接退出程序。
常见错误2:越界访问内存
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
解释:我们知道p是一个(int*)类型的指针,p+i则代表跳过i个整形而在此处的代码当中,当i等于10的时候,我们p跳过10个整形刚好跳过我们申请的空间,而此时在对p进行解引用操作,则算属于非法访问内存,程序会崩溃的。
解决办法:对于开辟出来空间的大小我们要牢记,防止非法访问其他内存的空间。
常见错误3:对非动态开辟的空间进行释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
这里的a是在栈区开辟的空间,而free函数是对动态内存开辟的空间进行释放的。所以这个程序是error的
解决办法:牢记free函数的作用,是对动态开辟的内存空间进行释放的。
常见错误4:释放一部分动态内存空间
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
解释:在这里p是开辟的空间的地址,且又是整形指针所以+1则代表跳过1个整形的地址,所以这里的p不在是指向内存的首地址了。所以只对申请空间的一部分进行了释放,还是会存在内存泄漏的,所以这也是一个错误。
解决办法:对于开辟出来的空间的地址我们不要进行移动。
常见错误5:对同一块内存空间进行多次释放
void test()
{
int*p=(int*)malloc(100);
free(p);
free(p);
}
解释:在这里对p进行了多次释放,第一次释放是正确的是对自己申请的空间进行释放,而第二次释放的时候就是错误的了,因为第二次的释放是对空间的非法访问。所以也是error的,当然在说这些前提下我们都认为我们申请的空间是有效的。
解决办法:开辟一次,释放一次。
常见错误6:对动态内存开辟的空间未释放,形成内存泄漏
void test()
{
int* p =(int*)malloc(100);
if(p!=NULL)
{
*p=20;
}
}
int main()
{
test();
while(1);
return 0;
}
解释:当我们调用test函数结束后,因为p是一个局部变量是在栈区上开辟的,所以出了test函数的作用域后,p就会销毁,而我们也为对开辟出来的空间进行释放,所以我们就会浪费这100个字节的空间,所以就会形成内存泄漏的问题。
解决办法:当我们开辟出来的空间没有进行使用得时候我们一定要记得释放这一块空间。
题目1:
void GetMemory(char* p)
{
p=(char*)malloc(100);
}
void Test(void)
{
char* str =NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
解释:首先这个程序是错误的,因为对NULL进行了非法访问,str一开始是NULL,GetMemory(str),想法是给str开辟一块空间,可是我们要记住,p是一个形式参数,会独自开辟内存空间,对形式参数的影响不会影响实参。且该函数还存在内存泄漏的问题
题目2:
char* GetMemory(void)
{
char p[] ="hello world";
return p;
}
void Test(void)
{
char* str =NULL;
str =GetMemory();
printf(str);
}
解释:在GetMemory函数当中,p是一个数组,p是指向"hello world",出函数就会销毁,而此时将p的值给str,尽管此时我们的str还是指向曾今的那块空间的地址,可是出了函数范围的时候,这块空间就会还给操作系统,所以此时的str是一个野指针打印出来的就是烫烫烫...属于返回栈空间地址的问题。
题目3:
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void test()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello world");
printf(str);
}
int main()
{
test();
return 0;
}
解释:这个程序可以打印出来结果,就是当我们使用完开辟的空间后未进行内存的释放,会形成内存泄漏的问题。
题目4:
void Test()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
解释:这段代码虽然可以在vs中打印出来,但是这段代码还是有问题的。
因为我们已近对str这块空间先释放的,所以已近将这块空间还给操作系统了,而此时str还是指向那块空间的地址,所以world还是会被拷贝进去。但是这已近形成非法访问内存了。所以我们在日常敲代码的过程中如果已近将某一块空间给释放了,我们一定要将指针置为空,这样我们才不会形成非法访问内存的可能。
感谢大家的观看!