目录
- 通常开辟的内存是固定的(数组的内存是编译时分配的,但申明时需指定长度);
- 针对已事先指定内存大小比较合适,对事先不确定的内存大小可使用动态内存函数;
一,动态内存函数
- 动态内存函数在堆区开辟空间;
malloc
void* malloc( size_t size )
- 在内存申请一块连续的空间(字节数),并返回指向这块空间的指针;
- 如开辟成功,则返回一个指向开辟好空间的指针;
- 如开辟失败,则返回一个NULL,因此返回值一定要做检查;
- 返回值类型为void*,使用时需自行决定;
- 如参数为0,malloc行为未定义,取决于编译器;
//在堆区,开辟4个整型16个字节的空间
int* p = (int*)malloc(4 * sizeof(int));
free
void free( void *memblock )
- 专门用来做动态内存的释放和回收的;
- 如参数指针指向的空间不是动态内存开辟的,那free行为是未定义的;
- 如参数指针为NULL,则函数不做任何操作;
- 释放后的指针将变为野指针,最好在设置为NULL,避免非法访问;
注:动态内存释放有两种方式
- free主动释放;
- 程序结束释放;
int main()
{
//在堆区,开辟4个整型16个字节的空间
int* p = (int*)malloc(4 * sizeof(int));
//检查p是否为空指针
if (p == NULL)
{
perror("main"); //p为空指针返回错误信息
return 0;
}
//释放动态内存空间
free(p);
p = NULL; //避免错误使用
return 1;
}
calloc
void *calloc( size_t num, size_t size )
- 开辟指定元素个数的内存,元素初始值为0;
- num为元素个数,size为每个元素的大小;
- 与malloc的区别,只在于每个字节初始化值为0;
int main()
{
//在堆区,开辟4个整型16个字节的空间
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
printf("%d", *p); //0
free(p);
p = NULL;
return 1;
}
realloc
void *realloc( void *memblock, size_t size )
- 调整动态内存空间的大小,让内存管理更加灵活;
- 如指向的空间之后有足够的内存空间,则直接追加;
- 如指向的空间之后没有足够的内存空间,则重新开辟新的内存空间,并将原来内存数据拷贝过来且释放其空间,最后返回新的空间地址;
- 用一个新的变量来接收realloc函数的返回值,realloc可能开辟失败返回NULL;
- 如对空指针进行操作,等同于malloc;
int main()
{
//在堆区,开辟4个整型16个字节的空间
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
//将p的空间拓展为10整型40个字节的空间
int* ptr = realloc(p, 10 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
free(p);
p = NULL;
return 1;
}
//二者等同
int* p = (int*)malloc(4 * sizeof(int));
int* p = (int*)realloc(NULL, 4 * sizeof(int));
二,常见的动态内存错误
- 对空指针进行解引用(动态内存可能开辟失败为NULL,使用前需进行判断);
- 对动态开辟空间的越界访问;
- 对非动态开辟的内存使用free释放;
- 使用free只释放了部分动态开辟的内存;
- 对同一块动态内存多次释放;
- 动态开辟内存忘记释放;
- 忘记释放不在使用的动态开辟的空间,会造成内存泄露(切记一定要释放且要正确释放);
//动态内存的指针可能为NULL,在进行解引用操作前,需进行判断;
int main()
{
int* p = (int*)malloc(10000000 * sizeof(int));
//使用前,需判断p是否为空
*p = 10;
return 0;
}
//对动态开辟空间的越界访问;
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
*(p+4) = 10;
free(p);
p = NULL;
return 1;
}
//对非动态开辟的内存使用free释放;
int main()
{
int i = 10;
int* p = &i;
free(p);
p = NULL;
return 1;
}
//使用free释放一块动态开辟内存的一部分;
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
free(p + 2);
p = NULL;
return 1;
}
//对同一块动态内存多次释放;
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
free(p);
free(p);
p = NULL;
return 1;
}
//忘记释放动态开辟内存(内存泄漏)
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
return 1;
}
注:
- 栈区:局部变量、函数形参等;
- 堆区:动态内存开辟;
- 静态区(数据段):全局变量、静态变量static;
经典试题
//试题一
void GetMemory(char* p)
{
//在堆区开辟内存
//忘记释放,内存泄露
p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(str); //传值(形式上虽然是地址),函数调用结束p已销毁,并未更改str
strcpy(str, "hello world");
printf(str);
return 0;
}
//结果:异常访问冲突
//修改1
char* GetMemory(char* p)
{
p = (char*)malloc(100);
return p;
}
int main( )
{
char* str = NULL;
str = GetMemory(str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
return 0;
}
//结果:hello world
//修改2
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
return 0;
}
//结果:hello world
//试题二
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
int main()
{
//函数调用结束p已销毁
//尽管p的值已赋给str,但所指的内存已还回系统
char* str = NULL;
str = GetMemory();
printf(str);
return 0;
}
//结果:烫烫烫烫烫烫烫烫
//修改
char* GetMemory(void)
{
//static关键字避免销毁(栈区变为静态区)
static char p[] = "hello world";
return p;
}
int main()
{
char* str = NULL;
str = GetMemory();
printf(str);
return 0;
}
//未释放
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
int main()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
//未判断
//释放,未置空
//访问已释放的空间
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
三,C/C++程序的内存开辟
栈区stack
- 执行函数时,函数内局部变量可在栈上创建,函数执行结束时自动释放;
- 栈区内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限;
- 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据,返回地址等;
堆区heap
- 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收;
- 分配方式类似于链表;
数据段(静态区)static
- 存放全局变量、静态数据;
- 程序结束后,由系统释放;
代码段
- 存放函数体(类似成员函数和全局变量)的二级代码;
- 常数;
四,柔性数组 — flexible array
- 在C99,结构中的最后一个元素永许是未知大小的数组,即为柔性数组成员;
- 结构中的数组,是大小可变的数组;
struct S
{
char c;
int arr[]; //或arr[0],此数组即为柔性数组成员
};
特点:
- 柔性数组成员前面必须至少一个其他成员;
- sizeof返回的大小不包括柔性数组内存;
- 包含柔性数组成员的结构,用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小;
struct S
{
char c;
int arr[];
};
int main()
{
printf("%d", sizeof(struct S)); //4
}
//会考虑对齐数
//柔性数组形式
struct S
{
int n;
int arr[];
};
int main()
{
//sizeof(struct S)是分配给n的
//10 * sizeof(int)是分配给arr的
struct S* p = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
if (p == NULL)
{
perror("main");
return 0;
}
free(p);
p = NULL;
return 1;
}
//指针形式
struct S
{
int n;
int* arr;
};
int main()
{
struct S* p = (struct S*)malloc(sizeof(struct S));
if (p == NULL)
{
perror("p");
return 0;
}
p->arr = (int*)malloc(10 * sizeof(int));
if (p->arr == NULL)
{
perror("p->arr");
return 0;
}
free(p->arr); //需先释放
p->arr = NULL;
free(p);
p = NULL;
return 1;
}
优势:
- 方便内存释放,多个malloc复杂且容易忘记释放;
- 有利于访问速度,连续的内存空间,无内存碎片;
注:局部性原理
- 空间局部性,下次会大概率会使用当前内存的周边区域;
- 时间局部性,被引用过一次的存储器位置在未来会被多次引用(通常在循环中);
五,案例