目录
动态内存管理
概念
malloc的全称是memory allocation,也就是 是动态内存分配函数
首先来看函数原型
函数原型为 void *malloc(unsigned int size) ;其作用是在内存的动态存储区中分配一个长度为size的连续空间。此函数的返回值是分配区域的起始地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
返回类型为 void *,该类型相当于一个通用型指针,最好在将返回的指针强制转换为需要的类型,这样可以提高可读性。
然而,把void 的指针赋给任意类型的指针完全不用考虑类型匹配问题。
如果malloc()分配内存失败,将返回一个空指针。
动态内存开辟常见错误
1,对NULL指针的解引用操作
int main()
{
int* p = (int*)malloc(100000000);
对malloc()函数的返回值做判空处理
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
return 0;
}
2,对动态开辟的空间做越界访问
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
int* p = (int*)malloc(100000000000);
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 40; i++)
{
*(p + 1) = i;
}
free(p);
p = NULL;
return 0;
}
3,使用free释放非动态开辟的空间
int main()
{
int arr[10] = { 0 };
int* p = arr;
free(p);
p = NULL;
return 0;
}
这种静态开辟的空间,不能用free()释放
4,使用free释放动态开辟内存的一部分
int main()
{
int* p = malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;
}
free(p);
p = NULL;
return 0;
}
不能中途释放;p指针在移动了之后,不能记住起始地址有内存泄露的风险
5,对同一块动态内存开辟的空间,多次释放
int main()
{
int* p = (int*)malloc(100);
使用;
释放;
free(p);
p = NULL;//添加为空指针之后,释放就不会出问题了
free(p);
return 0;
}
6,动态开辟的空间忘记释放
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
return 1;
}
}
int main()
{
test();
return 0;
}
其他
动态开辟的空间,两种回收方式:
第一种,主动释放;第二种,程序结束(并非函数结束)
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存动态管理经典笔试题
找出下面代码的错误:
void getmemory(char* p)
{
p = (char*)malloc(100); //没有free
}
void test(void)
{
char* str = NULL;
getmemory(str); //传值,不会更改str
strcpy(str, "hello world");
printf("%s\n",str);
}
int main()
{
test();
return 0;
}
1,str传给getmemory()函数的时候,是传值参数;
所以getmemory函数的形参p是str的一份临时拷贝;
在getmemory函数内部动态申请空间的地址,存放在p中,不会影响外边str,
所以当getmemory函数返回之后,str的值仍然是NULL;所以,strcpy会失败
2,getmemory函数返回之后,形参p销毁,使得动态开辟的100个字节存在内存泄露,无法释放。
改进上述题目,以便可用,下面介绍两个方法:
改进1:
char* getmemory()
{
char *p = (char*)malloc(100);
return p;
}
void test(void)
{
char* str = NULL;
str = getmemory();
strcpy(str, "hello world");
printf("%s\n", str);
printf(str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
//输出
hello world
hello world
改进2:
void getmemory(char** p)
{
*p = (char*)malloc(100);
}
void test(void)
{
char* str = NULL;
getmemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
//输出
hello world
将上述题目更改之后,再次查找题目中的错误:
char* getmemory(void)
{
char p[] = "hello world";
return p;
}
void test(void)
{
char* str = NULL;
str = getmemory();
printf(str);
}
int main()
{
test();
return 0;
}
错误:p是局部变量,当退出getmemory()函数时,p会被销毁,p指针指向的内存空间被回收。这时再使用返回的指针会造成非法访问。
原理解释:
1,getmemory()函数内部创建的数组是在栈上创建的
出了函数,p数组的空间就还给了操作系统,
返回的地址是没有实际意义的,如果通过返回的地址,去访问内存就是非法访问。
2,局部变量会被销毁,但是申请的动态内存空间不会被销毁。
malloc()只有两种情况才会被销毁:1,free();2,程序结束
局部变量实际上是在栈上;这类问题统称为栈上返回空间地址问题,出函数会被销毁。
malloc()实际上是在堆上,堆上返回的空间地址就不会报错,因为不会被销毁
小题目:
int* f2(void) {
int* p;
*p = 10;
return p;
}
野指针问题:这个p指针没有指向任何地址,但是却给赋值了
int* f1(void) {
int x = 0;
return (&x);
}
int main()
{
printf("%d\n", *f1);
}
局部变量问题:x是一个局部变量,出函数之后会被销毁,返回的值就无效了
void test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
printf("%s\n", str);
free(str);
str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
test();
return 0;
}
free()之后,一定要把指针置为空指针;要不然可能会引起非法访问
C/C++程序分配的几个区域:
1,栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数时分配的局部变量,函数参数,返回数据,返回地址等等。
2,堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时,可能由OS回收,分配方式类似于链表
3,数据段(静态区):(static修饰的)存放全局变量,静态数据。程序结束后由系统释放。
4,代码段:存放函数体(类成员和全局函数)的二进制代码。
下列哪一个不是指针类型?
#define int_ptr int*
typedef int* int_ptr1;
int_ptr a, b;
int_ptr1 c, d;
答案:b
#define相当于替换文本,typedef是一个新的类型。
柔性数组:
结构中的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员
举例:
struct s {
int i;
int arr[]; //大小未知,这个arr数组就称为柔性数组
int arr1[0]; //这样写也可以,大小写成0表示未知大小
};
int main()
{
期望arr的大小是10个整形
struct s* ps = (struct s*)malloc(sizeof(struct s) + 10 * sizeof(int));
ps->i = 10;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
增加空间
struct s* ptr = (struct s*)realloc(ps, sizeof(struct s) + 20 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
}
使用
释放
free(ps);
ps = NULL;
ptr = NULL;
struct s s1 = { 0 }; //正常使用不会这样创建
printf("%d\n", sizeof(s1));
return 0;
}
柔性数组的特点:
1,结构中的柔性数组成员前面至少有一个其他成员
2,sizeof()返回的这种结构大小,不包含柔性数组的内存
3,包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
我们也可以不使用柔性数组达到柔性数组的效果:
struct s {
int i;
int* arr;
};
int main()
{
期望arr的大小是10个整形
struct s* ps = (struct s*)malloc(sizeof(struct s));
if (ps == NULL)
return 1;
ps->i = 10;
ps->arr = (int*)malloc(10 * sizeof(int));
if (ps->arr == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
增加空间
int* ptr = (struct s*)realloc(ps->arr, 20 * sizeof(int));
if (ptr != NULL)
{
ps->arr = ptr;
}
使用
释放
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
free(ptr);
ptr = NULL;
struct s s1 = { 0 }; //正常使用不会这样创建
printf("%d\n", sizeof(s1));
return 0;
}
相比柔性数组,这种方式需要多获取和释放一次arr指向的一块空间;
多获取和多释放指的就是arr的那一块空间。
柔性数组的优势:
1,方便内存释放:
如果我们的代码是在一个给别人的函数中,你在里面做了二次分配。并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体的成员也需要free
所以,你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及成员要的内存一次性分配好了,并返回一个结构体指针,用户做一次free就可以把所有内存也给释放掉。
2,这样有利于访问速度:
连续的内存有利于提高访问速度,也有益于减少内存碎片,(其实,我个人也没觉得有多高了,反正你跑不了要用做偏移量的加法来寻址)