目录
目录
为什么存在动态内存分配?
其实就是因为常见的开辟空间的方法是死的。当它们在创建好的同时,它们也同时被固定了,不变了,当在未来想要添加新的项目进去的时候并且之前旧的空间是不够用的时候,问题就出现了
例如下面两种开辟的方式
int val = 20;//在栈空间上开辟四个字节char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
1. 空间开辟大小是固定的2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
将来就会出现一系列的麻烦,为此我们需要动态内存的开辟了
void* malloc (size_t size);
1.返回部分:返回 void* 的指针类型 -- 也就是说使用的时候需要强制转化类型返回值的类型 是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 (开辟失败的时候返回NULL) -- 一般是内存大小不够了(笑)
2.参数部分:无符号的整型 -- 也就是你想要开辟多少空间,如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器(不过开辟0字节是什么情况?--笑)
void free (void* ptr);
这两个通常是成对出现的,一个开辟空间,一个释放空间
需要注意的是free是释放动态内存的空间,是不能释放栈区的内存
#include
int main() {
int* p = NULL;
free(p); //释放一个空指针free是不会发生什么事情的return 0;
}
- //动态内存管理
-
- #include
- #include
//strerror使用 -- 转化出错误代码 --关于错误的信息在编译器里面保存了这里是拿出来 - #include
//errno使用 -- 这个的功能是存储最新的错误信息 - #include
-
- int main() {
- int arr[10] = { 0 };
- //动态开辟
- int* p = (int*)malloc(40); //强制类型转化为需要的 int类型
- if (p == NULL) //开辟失败的时候提前结束
- {
- printf("%s\n", strerror(errno)); //使用报错的一些函数
- return 1; //C习惯上,返回1 则是出现异常
- }
-
- //使用
- int i = 0;
- for ( i = 0; i < 10; i++)
- {
- *(p + i) = i;
- printf("%d ", *(p + i));
- }
- printf("\n");
-
- return 0; //返回0 表示正常返回
- }

当开辟失败的时候

注意:这个失败是我在vs2022下的x86(32位)环境下运行出来的,本人测试过x64(64位)环境下是可以开辟这么大的空间的(科技的进步啊)
内存申请空间的位置也是不一样的

当然我们还需要注意关于内存泄漏的问题,我在这里没有free是因为我的程序退出了,系统会自动回收,当然如果以后有要二十四小时一直运行的程序时就要十分小心这一点了。
假如malloc用多了容易在内存之间产生内存碎片,这里的内存碎片没有办法利用的化,那么就会造成内存利用率下降。还要一点是,堆区的空间是操作系统管理的,当要申请空间的时候,都要打断操作系统的执行,让操作系统申请。这样以来就会造成效率的下降和内存碎片等情况

补图

当想要不断的开辟空间的时候,现在的机器聪明很多了,不会出现一直开辟空间出现电脑不行的问题
- //malloc死循环
- #include
- int main()
- {
- while (1)
- {
- malloc(1);
- }
- return 0;
- }

注意:在二十四小时运行的程序中仍然需要注意这一点,不用的话要把内存释放,不然发生内存泄漏是很危险的行为
void* calloc (size_t num, size_t size);
- #include
- #include
- #include
- #include
- int main() {
-
- int* p = (int*)calloc(10, sizeof(int));
- if(p == NULL)
- {
- printf("%s\n", strerror(errno));
- return 1;
- }
- //打印
- int i = 0;
- for ( i = 0; i < 10; i++)
- {
- printf("%d ", *(p + i));
- }
- printf("\n");
- //释放
- free(p);
- p = NULL;
-
- return 0;
- }

特点
于此得知,它在使用的时候会把开辟的空间的内容初始化为0
void* realloc (void* ptr, size_t size);
解析
1.返回部分:返回一个 void* 的指针 -- 函数是不知道什么是类型的数据的
2.参数部分:(void*ptr)-- 指向以前用 malloc、 calloc 或 realloc 分配的内存块的指针,或者,这可以是一个空指针,在这种情况下分配一个新块(就像调用 malloc 一样)。
(size_t size) -- 内存块的新大小,以字节为单位
- #include
- #include
- #include
- #include
-
- int main() {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- printf("%s\n", strerror(errno));
- return 1;
- }
- //赋值初始化
- int i = 0;
- for ( i = 0; i < 10; i++)
- {
- *(p + i) = i;
- printf("%d ", *(p + i));
- }
- printf("\n");
- //扩容
- int *ptr = (int*)realloc(p, 80); //这里的80是新的空间的大小,也就是开辟了40字节个新的空间
- if (ptr != NULL)
- {
- p = ptr; //如果想继续用p来维护原来的数据,这样可以实现,
- } //而不应该使用p 来直接接收,防止扩容失败,p被赋值NULL,而找不到原来的地址了
- //使用
- for (i = 10; i < 20; i++)
- {
- *(p + i) = i;
- printf("%d ", *(p + i));
- }
- printf("\n");
- return 0;
- }


看看这两种情况

对NULL指针的解引用操作
- //对NULL指针的解引用操作
- #include
-
- int main() {
- int* p = (int*)malloc(40);
- if (p == NULL) //假如没有这里的if语句 当开辟失败的时候就会出问题
- return 1; //为此在使用动态开辟的时候要先判断是否成功开辟
- *p = 20;
- free(p); //用完要记得释放空间
- p = NULL;
-
- return 0;
- }
对动态开辟空间的越界访问
- //对动态开辟空间的越界访问
-
- #include
-
- int main()
- {
- int* p = (int*)malloc(40);
- if (p ==NULL)
- {
- return 1;
- }
- //越界了
- int i = 0;
- for ( i = 0; i <= 10; i++) //即使是动态开辟的空间也要防止越界访问
- {
- p[i] = i;
- }
- free(p);
- p = NULL;
-
- return 0;
- }
- //对非动态开辟内存使用free释放
-
- #include
-
- int main()
- {
- int a = 10;
- int* p = &a; //这里并不是动态开辟的空间是不能被释放的
- //…………
- free(p);
- p = NULL;
-
- return 0;
- }
- #include
-
- int main()
- {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- return 1;
- }
- //使用
- int i = 0;
- for (i = 0; i < 5; i++) //这里指到了中间的元素,是不能释放一部分的,程序会崩溃
- {
- *p = i;
- p++; //指针移动了,不再指向了开始的位置,因此是不能被释放的
- } //因此要改的话,应该写成 // p[i] 或者 *(p+i)
-
- //释放
- free(p);
- p = NULL;
-
- return 0;
- }

- #include
-
- int main()
- {
- int* p = (int*)malloc(40);
- //……
- free(p); //一旦free后就要马上 p = NULL ,防止p变为野指针
- //……
- free(p); //释放两次相同的空间,程序会崩溃掉
-
- return 0;
- }
- #include
- #include
- void test()
- {
- int* p = (int*)malloc(100);
- //……
-
- int flag = 0;
- scanf("%d", &flag);
- if (flag == 5)
- return;
-
- free(p); //这里就有可能会释放不了空间,从而造成内存的泄漏,并且这里是更彻底的泄漏
- p = NULL; //一旦函数返回就再也找不到了
- }
-
- int main()
- {
- test();
-
- return 0;
- }
- #include
-
- //一个开辟空间的函数
- int* test()
- {
- int* p = (int*)malloc(40);
- if (p == NULL)
- {
- return p;
- }
- //……
- return p;
- }
-
- int main()
- {
- int* ret = test(); //接收一个指针
- //后面忘记释放了,从而导致内存的泄漏
-
- return 0;
- }
- //下面调用Test会发生什么?
-
- #include
- #include
- #include
- void GetMemory(char* p)
- {
- p = (char*)malloc(100);
- }
- void Test(void) {
- char* str = NULL;
- GetMemory(str);
- strcpy(str, "hello world");
- printf(str);
- }
-
- int main()
- {
- Test();
- return 0;
- }

修改
- #include
- #include
- #include
- void GetMemory(char** p)
- {
- *p = (char*)malloc(100);
- }
- void Test(void) {
- char* str = NULL;
- GetMemory(&str);
- //str存放的就是动态开辟的100字节的地址
- strcpy(str, "hello world");
- printf(str);
- //要记得释放
- free(str);
- str = NULL;
- }
-
- int main()
- {
- Test();
- return 0;
- }
- #include
- char* GetMemory(void) {
- char p[] = "hello world"; //局部数组,在出函数的时候就会变成野指针
- return p; //空间在返回的时候,内容就有可能会被改了(当然也有可能不会)
- }
- void Test(void) {
- char* str = NULL;
- str = GetMemory();
- printf(str);
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
比如用以下的情况来说明


上面的问题都是属于返回栈空间地址的问题
- #include
- void GetMemory(char** p, int num) {
- *p = (char*)malloc(num);
- }
- void Test(void) {
- char* str = NULL;
- GetMemory(&str, 100);
- strcpy(str, "hello");
- printf(str);
- }
-
- int main()
- {
- Test();
- return 0;
- }
既然开辟了空间就要记得释放空间

- #include
- #include
- void Test(void) {
- char* str = (char*)malloc(100);
- strcpy(str, "hello");
- free(str);
- if (str != NULL)
- {
- strcpy(str, "world");
- printf(str);
- }
- }
-
- int main()
- {
- Test();
- return 0;
- }

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员
示例定义
- typedef struct st_type
- {
- int i;
- int a[0];//柔性数组成员
- }type_a;
- typedef struct st_type
- {
- int i;
- int a[];//柔性数组成员
- }type_a;
1.结构中的柔性数组成员前面必须至少一个其他成员。2.sizeof 返回的这种结构大小不包括柔性数组的内存。3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
第二点·补
- #include
- #include
- struct S
- {
- int n;
- int arr[];
- };
-
- int main()
- {
- struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
- if (ps == NULL)
- {
- return 1;
- }
- ps->n = 100;
- int i = 0;
- for ( i = 0; i < 10; i++)
- {
- ps->arr[i] = i;
- }
- for ( i = 0; i < 10; i++)
- {
- printf("%d ", ps->arr[i] = i);
- }
- printf("\n");
- free(ps);
- ps = NULL;
-
- return 0;
- }
下面的图我没有释放,记得需要释放的防止空间泄漏

当我们想要扩大arr的空间时只需要用realloc就行了
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 80); //这样就可以扩大继续使用了
if (ptr != NULL) //继续使用ps维护代码
{
ps = ptr;ptr = NULL; //把ptr置为空指针
} //加上这一段就行了
比起下面的写法上面的柔性数组更具有优势
- #include
- #include
- struct S
- {
- int n;
- int *arr;
- };
-
- int main()
- {
- struct S* ps = (struct S*)malloc(sizeof(struct S));
- if (ps == NULL)
- {
- return 1;
- }
- ps->n = 100;
- ps->arr = (int*)malloc(40);
- if (ps->arr == NULL)
- {
- return 1;
- }
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- ps->arr[i] = i;
- }
- for (i = 0; i < 10; i++)
- {
- printf("%d ", ps->arr[i] = i);
- }
- printf("\n");
- //扩容
- int* ptr = (int*)realloc(ps->arr, 80);
-
- if (ptr != NULL) //继续使用ps维护代码
- {
- ps = ptr;
- ptr = NULL; //把ptr置为空指针
- }
-
- //释放
- free(ps->arr); //因为开辟了两次,所以要释放两次空间,注意先后顺序
- free(ps);
- ps = NULL;
-
- return 0;
- }
另一种写法//没有柔性数组好,这个方法开辟的次数多,容易忘记释放,造成内存泄漏,
并且开辟多了还造成内存利用率降低
天若有情天亦老,人间正道是沧桑。 --致落入红尘的你