目录

零.前言

1.基本概念

1.什么是动态内存

2.开辟动态内存的作用

1.在栈区开辟的空间

2.在堆区开辟空间

2.动态内存开辟的函数

1.void *malloc( size_t size )

1.含义

2.参数

3.返回值

4.用法

5.注意事项

2.void free( void *memblock )

1.含义

2.参数

3.返回值

4.用法

5.注意事项

3.void *calloc( size_t num, size_t size )

1.含义

2.参数

3.返回值

4.用法

4.void *realloc( void *memblock, size_t size )

1.含义

2.参数

3.返回值

4.用法

3.动态内存开辟中常见的错误

1.对空指针的解引用操作

2.动态内存开辟的越界访问

3.对非动态内存开辟的空间使用了free

4.使用free释放开辟空间的一部分

5.对一块内存进行多次释放

6.动态内存开辟忘记释放

4.经典笔试题

1.笔试题1

2.笔试题2

3.笔试题3

4.笔试题4

5.柔性数组

1.含义

2.特点

3.举例

4.柔性数组的优势

1.方便内存释放

2.有利于提高访问速度

6.总结


零.前言

时间与空间,构成了我们处的这个世界,在神奇宝贝中,分别由帝牙卢卡和帕路奇犽所掌管。但在我们码农的世界里,无数大佬朝朝思,夜夜想如何节省空间的同时又能够节省时间,于是发明了好多复杂度的计算方法,如果说在栈中的存储是为了时间考虑,那动态内存的开辟则是为了空间的优势。

1.基本概念

1.什么是动态内存

大家看这样一幅图片,我们通常定义的变量是在栈区为他分配空间,而如果使用malloc,calloc,realloc这样的函数就是在堆区为他开辟空间。

1.栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些内存单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率更高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等。

2.堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。

3.数据段(静态区):存放全局变量,静态数据,程序结束后由系统释放。

4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。

有了这张图我们也可以理解static关键字的含义了:

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用于就销毁。

但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束时才销毁所以生命周期变长。

2.开辟动态内存的作用

1.在栈区开辟的空间

#include<stdio.h>
void print(int* n)
{int a = 10;n = &a;
}
int main()
{int* p = NULL;print(p);printf("%d", *p);return 0;
}

这段代码就是问题代码,虽然在函数中p通过n确实指向了a这块空间,但是出函数之后,a这段空间就释放了,使得p变成了一个野指针,所以不能进行解引用。

2.在堆区开辟空间

在堆区开辟的空间,空间是否释放是由用户决定的,并不是出某个空间就自动释放,下面就来介绍如何在堆区开辟空间。

动态内存开辟相对于在栈区开辟空间还有一个好处,当在栈区开辟空间时,需要一下子全都开辟完,比如开辟了一个1000个元素的数组,但最终只使用了30个元素,那么其他970个空间就发生了浪费,而在堆区开辟空间,可以用到哪里开辟到哪里,因为即使退出了加入数据的元素,之前加入的元素也不会被释放,再加入元素时只需要再次调用这个函数就可以了。

2.动态内存开辟的函数

1.void *malloc( size_t size )

1.含义

在堆区开辟空间。

2.参数

size表示开辟的空间为size个字节。

3.返回值

返回一个空类型的指针,该指针指向开辟的空间的首地址。

4.用法

由于我们返回的是一个空类型的指针,所以在我们想要使用这段空间的内容时,需要进行强制类型的转换,比方说我们在堆中存放了10个整型,那么接收的时候就要用整型指针进行接收。

int i;
int* p=(int*)malloc(40);//在堆上开辟40个字节,并将首地址赋值给p
for(i=0;i<10;i++)
{
*(p+i)=i;
}//将这段空间赋值

这样就成功在堆区开辟了一个40个字节的空间,用于存放10个整型(因为解引用时是4个字节一次的访问)。

5.注意事项

1.如果开辟空间成功则返回指向这块空间的指针。

2.如果开辟失败则返回一个空指针(NULL)。

3.返回值的类型是void*所以malloc函数不知道开辟这段空间的类型,具体的使用由使用者自己定义。

4.如果size的大小是0,malloc的标准是未定义的,行为取决于编译器。

2.void free( void *memblock )

1.含义

将动态内存开辟的空间还给操作系统。

2.参数

*memblock指动态内存开辟的空间的首地址。

3.返回值

无返回值。

4.用法

int i;
int* p=(int*)malloc(40);//在堆上开辟40个字节,并将首地址赋值给p
for(i=0;i<10;i++)
{
*(p+i)=i;
}//将这段空间赋值
free(p);//将开辟的40个字节的空间释放掉
p=NULL;//将p置为空

动态内存开辟的空间只有在程序运行结束时或者free掉才能还给操作系统,如果等程序运行结束,就很有可能出现内存崩溃的情况,所以我们引入free函数来回收空间。

在free函数回收空间之后,p指针变成野指针,需要用NULL赋值。

5.注意事项

1.如果p指向的空间不是动态内存开辟的,那么free函数的行为是未定义的。

2.如果p是空指针,那么free函数什么都不做。

3.void *calloc( size_t num, size_t size )

1.含义

在堆中开辟一段空间并初始化为0。

2.参数

num表示开辟了几个元素的空间,size表示一个元素开辟多大的空间。

3.返回值

返回一个空类型的指针,指向开辟空间的首元素的地址。

4.用法

实际上只是比malloc函数多了一个初始化的功能。

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(7, 5);//开辟七个元素,每个元素大小为5个字节return 0;
}

我们在内存中可以看到一共开辟了35个字节的空间,且值均赋值为0。

4.void *realloc( void *memblock, size_t size )

1.含义

调整动态开辟空间的大小。

2.参数

memblock指向的是要更改大小的动态开辟的空间的首元素。size表示的是要增加几个字节的。

3.返回值

返回的是更改之后空间的首元素的地址。

4.用法

由于不确定在原有空间的基础上增加空间,是否会成功,所以在使用realloc函数的时候有两种情况:

第一种:

我们知道在开辟空间的时候,开辟的空间在内存中是随机分布的。当我们在原有空间基础上增加空间时,如果原有空间后有足够的空间可供增加的时候我们是直接进行增加的。

第二种:

当原有空间之后的空间不够进行再增加空间的时候,会对原有空间进行一份拷贝,再增加空间。那么此时realloc返回的就是拷贝后空间的首元素的地址。

#include<stdio.h>
#include<stdlib.h>
int main()
{int i;int* p = (int*)malloc(40);//在堆上开辟40个字节,并将首地址赋值给pfor (i = 0; i < 10; i++){*(p + i) = i;}//将这段空间赋值realloc(p, 40);//在原有空间基础上增加了40个字节for (i = 0; i < 20; i++){*(p + i) = i;}for (i = 0; i < 20; i++){printf("%d\n", *(p + i));}free(p);p=NULL;//释放p指向的空间并将其初始化为0return 0;
}

打印的结果是:

即添加成功。

3.动态内存开辟中常见的错误

1.对空指针的解引用操作

在开辟动态内存时,不一定每一次都能够开辟成功(当然大部分都能开辟成功),所以我们需要判断一下返回的指针是否为空,即开辟内存之后要进行一次判断。

int* p=(int*)malloc(30);
if(p==NULL)
{
return -1;//如果开辟失败返回-1
}

2.动态内存开辟的越界访问

int* p=(int*)malloc(30);
if(p==NULL)
{
return -1;//如果开辟失败返回-1
}
int i;
for(i=0;i<20;i++)
{
*(p+i)=i;
}

这里对动态内存进行了越界访问,一共开辟了30个字节,但是却访问了80个字节。

3.对非动态内存开辟的空间使用了free

a=10;
int* p=&a;
free(p);

free的应用范围只能是动态内存开辟的。

4.使用free释放开辟空间的一部分

int* p=(int*)malloc(40);
p++;
free(p);此时p不指向起始元素地址,释放的是后一部分的空间

5.对一块内存进行多次释放

int* (int*)malloc(40);
free(p);//释放p的空间,此时p是一个野指针
free(p);//对野指针进行空间释放,是不对的

6.动态内存开辟忘记释放

void test()
{
int* p=(int*)malloc(100);
if(NULL!=p)
{
*p=20;
}
}
int main()
{
test();
while(1);
}

来看这一段代码,由于1永远是真,所以这段代码是无法执行结束的,所以p所指向的空间永远得不到释放,你可能会说实际工作中没有执行不完的代码,但如果工程量巨大,如果不释放空间的话,在程序结束之前,堆区可能很快就满了,后序的工作就无法进行了。

4.经典笔试题

1.笔试题1

void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
} 

我们运行程序发现程序挂掉了:

1.str传给p的时候是值传递,p是str的临时拷贝,malloc开辟的空间起始地址放在p中不会影响str,str仍为NULL

2.str是NULL,strcpy想把hello world拷贝到str指向的空间时,程序就崩溃了,因为NULL指向的空间不能访问。

3.内存泄漏,没有free

2.笔试题2

#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{   Test();return 0;
}

显然这段代码也挂掉了,这是由于在GetMemory开辟的空间在出函数时就销毁了,所以str接收的仍然是一个野指针。

3.笔试题3

#include<stdio.h>
#include<stdlib.h>
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()
{   void Test();return 0;
}

这段代码打印的结果是:

虽然打印出来了结果是hello,这里传递的是str的地址,用p来接收str的地址,对str地址解引用得到str本身,使str指向100个字节大小的空间,然后销毁p的空间。销毁的意思是不在程序中默认不能访问str的地址了,不过没关系,因为str已经指向了我们想让他指向的内容。

4.笔试题4

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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;
}

这段代码打印的结果是:

虽然打印出来了,但是str已经被free掉了,所以str是一个野指针,不能进行拷贝。

5.柔性数组

1.含义

在C99标准中,结构体的最后一个元素允许是未知大小的数组。

2.特点

1.结构中的柔性数组成员前面必须至少有一个其他成员。

2.sizeof返回这种结构大小中不包含柔性数组的内存。

3.包含柔性数组成员结构用malloc()函数进行动态内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

3.举例

#include<stdio.h>
struct A {int i;int a[];
};
int main()
{printf("%d", sizeof(struct A));
}

我们可以看出打印类型大小时并没有算柔性数组a的大小。

#include<stdio.h>
struct A {int i;int a[];
};
int main()
{struct A* p = (struct A*)malloc(sizeof(struct A) + 5 * sizeof(int));int i = 0;for (i = 0; i < 5; i++){p->a[i] = i;}for (i = 0; i < 5; i++){printf("%d ", p->a[i]);}return 0;
}

在为柔性数组开辟空间的时候,要在为结构体开辟空间之后再加上想要开辟的字节数。

4.柔性数组的优势

1.方便内存释放

如果我们的代码是在一个给别人使用的函数中,你在里面做了一个二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事情,所以,如果我们把结构体的内存以及其成员要的内存一次性分配就好了,并返回给用户一个结构体指针,用户用一次free就可以把所有的内存给释放掉。

2.有利于提高访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

6.总结

在第一次做数据结构实验课的时候,链表部分就需要用到动态内存的开辟,曾经蒙了很长一段时间,动态内存开辟使我们对内存的把握更加灵活,就是访问速度慢了一些,但是总会有代价的不是吗,栈区和堆区,一个是用空间来换取时间,一个是用时间来换取空间。我们可以根据复杂度去计算,也可以把这两种方式当薛定谔的猫的问题来处理。

在堆区开辟内存(动态内存的开辟)相关推荐

  1. c语言malloc引用类型作参数,C语言动态内存函数的理解和总结

    第一:内存的使用 内存可以分为以下三个主要的部分:栈区.堆区.静态区 栈区(stack):存放的是局部变量.函数的形参等都是在该区上存放的. 堆区(heap):动态内存函数开辟的空间.比如malloc ...

  2. C语言:动态内存分配

    个人博客网址:https://ljsblog.com 动态内存分配(十) 在不知道所需要的空间大小的情况下,这时就可以使用动态内存开辟. 当开辟的空间不再使用时,用free函数来释放calloc.ma ...

  3. 【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  4. 动态内存管理(开辟以及释放动态内存空间)

    文章目录 前言 malloc函数 calloc函数 realloc函数 free函数 - 避免内存泄漏 常见的动态内存错误 前言 如果我们被问道:如何创建一个可以根据用户需求来开辟大小的数组? 可能有 ...

  5. 动态内存的开辟与释放

    //动态内存分配 #include <stdlib.h> #include <stdio.h> #include <string.h> #include <e ...

  6. c语言————开辟动态内存空间

    如何使用c语言开辟一块动态内存内存空间: #include<stdio.h> #include<stdlib.h> struct s {int n;int arr[0];//内 ...

  7. C语言中动态内存分配的本质是什么?

    摘要:C语言中比较重要的就是指针,它可以用来链表操作,谈到链表,很多时候为此分配内存采用动态分配而不是静态分配. 本文分享自华为云社区<[云驻共创]C语言中动态内存分配的本质>,作者: G ...

  8. C语言之动态内存管理

    目录 为什么存在动态内存管理 动态内存函数 malloc和free函数 calloc realloc 动态内存的常见错误 1对空指针的解引用操作 2对动态开辟空间的越界访问 3使用free来释放非动态 ...

  9. 【C进阶】动态内存管理

    ⭐博客主页:️CS semi主页 ⭐欢迎关注:点赞收藏+留言 ⭐系列专栏:C语言进阶 ⭐代码仓库:C Advanced 家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们 ...

  10. 对于动态“内存”分配,Have you explored?(C语言)

    对于我们学习一门计算机编程语言,"内存"是我们永远绕不开的一个词.对于初始C语言的小伙伴来说:我们只要知道内存被分为栈区,堆区,静态区即可. 1.栈区:主要是用来存放"局 ...

最新文章

  1. java变量数据类型_Java——变量和数据类型
  2. 忘掉 Java 并发,先听完这个故事。。。
  3. linux开机自动启动数据库,mysql随linux开机自动启动
  4. C语言 socket 编程学习
  5. 发现几个常用的asp.net MVC Helper 源码
  6. CentOS查看分区的方式
  7. 618 技术特辑(一)不知不觉超预算3倍,你为何买买买停不下来?
  8. GARFIELD@04-13-2005
  9. 生成对抗网络系列—CycleGAN
  10. C/C++ 错误处理
  11. springboot整合XXL-JOB实行动态定时任务
  12. 【历史上的今天】4 月 3 日:亚马逊卖出第一本书;世界上第一通手机电话;IBM 计算机先驱出生
  13. 云游戏拉开产业化大幕
  14. CSDN20181217博客黑板报
  15. HIVE-启动服务-启动DG连接-迁移数据LINUX-HDFS-HIVE
  16. 12个优雅的 python 代码使用案例
  17. redis之lua脚本: 原子性 调试 嵌入高级语言
  18. 跟涛哥一起学嵌入式 25:我接触过近50块嵌入式开发板,分享一下教训和总结...
  19. 校园超市购物小程序 计算机毕业设计
  20. DISC 免费性格测试题

热门文章

  1. openFOAM的基础类型汇总
  2. 人工智能轨道交通行业周刊-第12期(2022.8.29-9.4)
  3. 乐视网正式聘用刘延峰担任公司总经理 任期三年
  4. MacBook Air 2014 安装NVME硬盘并纯UEFI安装和引导Win7
  5. overriding managed version警告
  6. PHP网页设计实现增删改查,包含报告实训内容
  7. wps怎么把xlsx转成html,怎样把wps转换成excel
  8. 农业统计分析系列2-试验设计
  9. 锚文本链接用html怎么做,锚文本链接是什么?
  10. python中转义符的用法大全_Python中的各种转义符\n\r\t