#include "test.h"
#include <stdlib.h>
#include <stdio.h>
一、内存分区
栈区:局部变量,函数形参
堆区:动态内存开辟(malloc/free/calloc/realloc)
静态区:全局变量,静态变量
二、动态内存开辟函数介绍
1.malloc
2.free
int main()
{
//假设开辟10个整型空间
int arr[10]; //栈区
//动态内存开辟的
int* p = (int*)malloc(10 * sizeof(int)); //堆区
//使用这些空间的时候
if (p == NULL)
{
perror("main"); //main:xxxxxx
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d\n", p[i]);
}
//回收空间
free(p);
//自己动手把p置为NULL
p = NULL;
return 0;
}
int main()
{
int a = 10;
int* pa = &a;
//free(pa); //err!free函数只能释放堆区动态开辟的空间
free(NULL); //什么事都不做
return 0;
}
3.calloc
与malloc的区别
(1)参数有两个,为num个大小为size的元素开辟一片空间
(2)把空间的每个字节初始化为0
int main()
{
//int* p = malloc(10 * sizeof(int));
//if (p == NULL)
// return 1;
//int i = 0;
//for (i = 0; i < 10; i++)
//{
// printf("%d\n", *(p + i)); //都是随机值
//}
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i)); //都是被初始化过的0
}
return 0;
}
4.realloc
void* realloc(void* memblock, size_t size)
第一个参数为指向要调整空间的指针,第二个参数为调整后的开辟内存大小
有可能返回旧的地址(原有空间后面足够),有可能返回新的地址(空间不足,找到了新空间),有可能返回NULL(无法开辟)
int main()
{
int* p = calloc(10, sizeof(int));
if (p == NULL)
{
perror("main");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 5;
}
//还需要p指向的空间更大,需要20个int的空间
//realloc调整空间
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr)
{
p = ptr;
}
//如果memblock之后的内存不够存放要开辟的空间
//(1)在内存中重新找一块新的空间
//(2)把原本空间内容拷贝到新的空间
//(3)释放原本空间
//(4)返回新空间起始地址
//如果在内存其他位置也不够存放要开辟的空间
//realloc有可能找不到合适的空间来调整大小,这时返回NULL,因此不直接把返回值赋给原指针
free(p);
p = NULL;
return 0;
}
int main()
{
int* p = (int*)realloc(NULL,sizeof(int)*10); //这里功能类似于malloc,就是直接在堆区开辟40个字节
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + 1)); //随机值
}
free(p);
return 0;
}
三、动态内存开辟常见的错误
1.对NULL指针进行解引用操作
int main()
{
int* p = (int*)malloc(10000000000 * 10000000000);
//要对malloc返回值做判断! if(p==NULL)
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i; //无法解引用空指针和空指针+i
}
free(p);
return 0;
}
2.动态开辟空间的越界访问
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 40; i++) //误以为开辟了40个元素,其实是40个字节
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
3.使用free释放非动态开辟的空间
int main()
{
int arr[10] = { 0 }; //栈区
int* p = arr;
//使用
free(p);
p = NULL;
return 0;
}
4.使用free只释放动态内存的一部分
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("main");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;
}
free(p); //err!首先,找不到p原本的位置;其次,只释放动态内存的一部分
p = NULL;
return 0;
}
5.对同一块动态开辟的空间多次释放 - 内存泄漏 - 比较严重的
int main()
{
int* p = (int*)calloc(100,sizeof(int));
//使用
//释放
free(p);
//为了避免err,释放完p后要置为NULL
//再次释放
free(p);
return 0;
}
6.动态开辟的空间忘记释放
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
return;
}
//使用
}
int main()
{
test();
//出了函数后,函数内部创建栈顶的p销毁,但其开辟的空间没有销毁,再也没法找到这块空间
//....
return 0;
}
动态开辟的空间,两种回收方式
1.主动free
2.程序结束 - 如果程序长期运行不结束,内存泄漏可能导致内存空间用完,因此要记得用完后主动free开辟的动态内存
练习题
1.
void GetMemory(char* p)
{
p = (char*)malloc(100); //形参p是str的一份临时拷贝,在GetMemory函数内部申请动态内存,存放在p中,不会影响外面的str
} //GetMemory函数返回时形参p销毁,使得动态开辟的100个字节存在内存泄漏
void Test(void)
{
char* str = NULL;
GetMemory(str); //把NULL传参,是值传递,形参是临时拷贝
strcpy(str, "hello world"); //相当于还是把helloworld拷贝给空指针
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
修改:
char* GetMemory1()
{
char* p = (char*)malloc(100);
return p;
}
void GetMemory2(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str1 = NULL;
str1 = GetMemory1();
strcpy(str1, "hello world");
printf(str1); //写法正确!
//printf("hello world"); //const char* p = "hello world",传给printf的实际上是h的地址
free(str1);
str1 = NULL;
puts("\n");
char* str2 = NULL;
GetMemory2(&str2);
strcpy(str2, "hello world");
puts(str2);
free(str2);
str2 = NULL;
}
int main()
{
Test();
return 0;
}
2.
GetMemory内部创建的数组是在栈上的,出了函数p数组的空间就还给了操作系统
返回的地址没有实际意义,如果通过返回的地址去访问内存就是非法访问内存
char* GetMemory(void)
{
char p[] = "hello world";
return p; //返回栈空间地址的问题
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
int *f2(void)
{
int* ptr;
*ptr = 10; //没有初始化,野指针
return ptr;
}
int main()
{
int* a = f2();
printf("%d\n", a);
}
3.
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str); //没有free(str)
}
int main()
{
Test();
return 0;
}
4.
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str); //free之后要置空!
if (str != NULL) //str没有置空,仍然指向已经被free过的空间,非法访问
{
strcpy(str, "world");
printf(str);
}
}
#define INT_PTR int*
typedef int* int_ptr;
INT_PTR a, b; //int* a,b - 指针变量不能连续定义,除非int* a,*b!
int_ptr c, d; //typedef重命名一种完整类型,是独立的
C/C++中程序内存区域划分
内核空间(用户代码不能读写)
栈(向下增长)
内存映射段(文件映射、动态库、匿名映射)
堆(向上增长)
数据段(全局数据、静态数据) - 静态区
代码段(可执行代码/只读常量)
四、柔型数组
C99中,结构的最后一个成员允许是未知大小的数组,这就叫做[柔型数组]成员
特点:
1.柔型数组成员之前必须至少有一个成员
2.sizeof返回的这种结构大小不包括柔型数组的大小
3.包含柔型数组成员的结构用malloc()函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔型数组的预期大小
struct S
{
int n;
int arr[]; //大小是未知
};
struct S
{
int n;
int arr[0]; //大小是未知
};
int main()
{
//printf("%d\n", sizeof(struct S)); //4
//如果期望arr大小是10个整型
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); //malloc开辟的空间可以用calloc重新分配,因此有"柔性"
if (ps != NULL)
{
ps->n = 10;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//增容
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20*sizeof(int));
//使用
if (ptr)
{
int i = 0;
for (i = 0; i < 20; i++)
{
ptr->arr[i] = i;
printf("%d ", ptr->arr[i]);
}
}
//释放 - 二者指向同一片空间
free(ps);
ps = NULL;
ptr = NULL;
return 0;
}
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
return 1;
ps->n = 10;
ps->arr = 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 = (int*)realloc(ps->arr, 20 * sizeof(int));
if (ptr != NULL)
{
ps->arr = ptr;
for (i = 0; i < 20; i++)
{
ps->arr[i] = i;
}
}
//释放
free(ps->arr);
ps->arr = NULL;
free(ps);
return 0;
}
柔型数组与指针成员相比优点:
1.指针成员需要两次malloc,两次free,容易出错
柔型数组方便内存释放
2.堆申请的空间之间有内存碎片,内存碎片的存在让内存利用率降低(空间局部性)
柔型数组连续的内存有益于提高访问速度,也有益于减少内存碎片