C语言深度剖析书籍学习记录 第五章 内存管理

 常见的内存错误

  • 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。

结构体成员指针未初始化

  •  很多初学者犯了这个错误还不知道是怎么回事。这里定义了结构体变量 stu,但是他没 想到这个结构体内部 char *name 这成员在定义结构体变量 stu 时,只是给 name 这个指针变 量本身分配了 4 个字节。name 指针并没有指向一个合法的地址,这时候其内部存的只是一 些乱码。所以在调用 strcpy 函数时,会将字串"Jimy"往乱码所指的内存上拷贝,而这块内存 name 指针根本就无权访问,导致出错。解决的办法是为 name 指针 malloc 一块空间。 同样,也有人犯如下错误:

  •  为指针变量 pstu 分配了内存,但是同样没有给 name 指针分配内存。错误与上面第一种 情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给 name 指针分 配了内存。

  • 不管什么时候,我们使用指针之前一定要确保指针是有效的。
  • 一般在函数入口处使用 assert(NULL != p)对参数进行校验。在非参数的地方使用 if(NULL != p)来校验。但这都有一个要求,即 p 在定义的同时被初始化为 NULL 了。比 如上面的例子,即使用 if(NULL != p)校验也起不了作用,因为 name 指针并没有被初始 化为 NULL,其内部是一个非 NULL 的乱码。 
  • 使用指针之前需要对其赋值为NULL   使用完之后也需要将其赋值为 NULL
  • assert 是一个宏,而不是函数,包含在 assert.h 头文件中。如果其后面括号里的值为假, 则程序终止运行,并提示出错;如果后面括号里的值为真,则继续运行后面的代码。这个 宏只在 Debug 版本上起作用,而在 Release 版本被编译器完全优化掉,这样就不会影响代码 的性能。
  • 有人也许会问,既然在 Release 版本被编译器完全优化掉,那 Release 版本是不是就完 全没有这个参数入口校验了呢?这样的话那不就跟不使用它效果一样吗?
    是的,使用 assert 宏的地方在 Release 版本里面确实没有了这些校验。但是我们要知道, assert 宏只是帮助我们调试代码用的,它的一切作用就是让我们尽可能的在调试函数的时候 把错误排除掉,而不是等到 Release 之后。它本身并没有除错功能。再有一点就是,参数出现错误并非本函数有问题,而是调用者传过来的实参有问题。assert 宏可以帮助我们定位错误,而不是排除错误。

为指针分配的内存太小

  • char *p1 = “abcdefg”;
  • char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
  • strcpy(p2,p1);
  • p1 是字符串常量,其长度为 7 个字符,但其所占内存大小为 8 个 byte。初学者往往忘 了字符串常量的结束标志“\0”。这样的话将导致 p1 字符串中最后一个空字符“\0”没有被 拷贝到 p2 中。解决的办法是加上这个字符串结束标志符:
  • char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));
  • 这里需要注意的是,只有字符串常量才有结束标志符。比如下面这种写法就没有结束标志符 了:
  • char a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};
  • 另外,不要因为 char 类型大小为 1 个 byte 就省略 sizof(char)这种写法。这样只会使你的代码可移植性下降。

内存分配成功,但并未初始化

  • 犯这个错误往往是由于没有初始化的概念或者是以为内存分配好之后其值自然为0。未初始化指针变量也许看起来不那么严重,但是它确确实实是个非常严重的问题,而且往往 出现这种错误很难找到原因。
  • int i = 10;  char *p = (char *)malloc(sizeof(char)); 但是往往这个时候我们还不确定这个变量的初值,这样的话可以初始化为 0 或 NULL。
  • int i=0;  char *p = NULL; 如果定义的是数组的话,可以这样初始化: int a[10] = {0};  或者用 memset 函数来初始化为 0:  memset(a,0,sizeof(a));
  • memset 函数有三个参数,第一个是要被设置的内存起始地址;第二个参数是要被设置 的值;第三个参数是要被设置的内存大小,单位为 byte。
  • 至于指针变量如果未被初始化,会导致 if 语句或 assert 宏校验失败。因为指针指向数据存放的是乱码,但是系统认为有数据是正确的,所以不对其存储的数据和默认的数值进行比较就无法判定数据的有效性

内存越界

  • 内存分配成功,且已经初始化,但是操作越过了内存的边界。 这种错误经常是由于操作数组或指针时出现“多 1”或“少 1”,常发生于循环遍历
  • 所以,for 循环的循环变量一定要使用半开半闭的区间,而且如果不是特殊情况,循环变 量尽量从 0 开始

内存泄露

  • 会产生泄漏的内存就是堆上的内存(这里不讨论资源或句柄等泄漏情况),也就是说由 malloc 系列函数或 new 操作符分配的内存。如果用完之后没有及时 free 或 delete,这块内存 就无法释放,直到整个程序终止
  • (void *)malloc(int size)
  • malloc 函数的返回值是一个 void 类型的指针,参数为 int 类型数据,即申请分配的内存 大小,单位是 byte。内存分配成功之后,malloc 函数返回这块内存的首地址。你需要一个指 针来接收这个地址。但是由于函数的返回值是 void *类型的,所以必须强制转换成你所接收 的类型。也就是说,这块内存将要用来存储什么类型的数据。
  • 比如:char *p = (char *)malloc(100);在堆上分配了 100 个字节内存,返回这块内存的首地址,把地址强制转换成 char *类型后赋 给 char *类型的指针变量 p。同时告诉我们这块内存将用来存储 char 类型的数据。也就是说 你只能通过指针变量 p 来操作这块内存。这块内存本身并没有名字,对它的访问是匿名访 问。
  • 上面就是使用 malloc 函数成功分配一块内存的过程。但是,每次你都能分配成功吗? 不一定。使用 malloc 函数同样要注意这点:如果所申请的内存块大于目前堆上剩余内存块(整块),则内存分配 会失败,函数返回 NULL。注意这里说的“堆上剩余内存块”不是所有剩余内存块之和,因为 malloc 函数申请的是连续的一块内存。
    既然 malloc 函数申请内存有不成功的可能,那我们在使用指向这块内存的指针时,必 须用 if(NULL != p)语句来验证内存确实分配成功了。

使用malloc函数申请 0 字节内存

  • 另外还有一个问题:用 malloc 函数申请 0 字节内存会返回 NULL 指针吗?
  • 可以测试一下,也可以去查找关于 malloc 函数的说明文档。申请 0 字节内存,函数并不返回 NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为 0 的内存。这 好尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。对于这 一点一定要小心,因为这时候 if(NULL != p)语句校验将不起作用。

内存释放

  • 既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存 却在空闲。与 malloc 对应的就是 free 函数了。free 函数只有一个参数,就是所要释放的内 存块的首地址。
  • 比如上例:   free(p);
  • free 函数看上去挺狠的,但它到底作了什么呢?其实它就做了一件事:斩断指针变量与这块内存的关系。比如上面的例子,我们可以说 malloc 函数分配的内存块是属于 p 的,因 为我们对这块内存的访问都需要通过 p 来进行。free 函数就是把这块内存和 p 之间的所有关系斩断。从此 p 和那块内存之间再无瓜葛。至于指针变量 p 本身保存的地址并没有改变, 但是它对这个地址处的那块内存却已经没有所有权了。那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。
    这就是 free 函数的功能。
  • 按照上面的分析,如果对 p 连续两次以上使用 free 函数,肯 定会发生错误。因为第一使用 free 函数时,p 所属的内存已经被释放,第二次使用时已经无 内存可释放了。关于这点,我上课时让学生记住的是:一定要一夫一妻制,不然肯定出错。内存释放之后需要将指针重新设置为NULL

内存已经被释放了,但是继续通过指针来使用

  • 这里一般有三种情况:
  • 第一种:就是上面所说的,free(p)之后,继续通过 p 指针来访问内存。解决的办法 就是给 p 置 NULL。
  • 第二种:函数返回栈内存。这是初学者最容易犯的错误。比如在函数内部定义了一个 数组,却用 return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。
  • 第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办 法是重新设计程序,改善对象之间的调用关系。
请使用手机"扫一扫"x

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446124.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么改电脑网络ip地址_抛弃重启路由器获取ip地址方式,巧妙运用ip代理改IP工具...

网络是简单的也是复杂的,在如此庞大的网络世界里有太多的不确定因素,导致我们遇到IP限制问题,从而影响到我们的网络访问,而大家都知道,如果遇到ip被限制的问题,最快速直接的办法就是把被限制的ip更换一个新…

C语言深度剖析书籍学习记录 第六章 函数

函数的好处 1、降低复杂性:使用函数的最首要原因是为了降低程序的复杂性,可以使用函数来隐含信息,从而使你不必再考虑这些信息。2、避免重复代码段:如果在两个不同函数中的代码很相似,这往往意味着分解工作有误。这时,应该把两个…

如何把word分装到两个byte_如何核对两个Word文档的内容差别?同事加班半小时,我只花了30秒...

昨天下班前,老板突然发了两份Word文档过来,一份是原稿,还有一份是修订稿,叫我们找出两份文档的内容差别之处,我只花了30秒就搞定了,然后准时下班!你想知道我是怎么操作的吗?下面小源…

stm32f767中文手册_ALIENTEK 阿波罗 STM32F767 开发板资料连载第五章 SYSTEM 文件夹

1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子第五章 SYSTEM 文件夹介绍第三章,我们介绍了如何在 MDK5 下建立 STM32F7 工程。在这个新建的工程之…

手机安卓学习 内核开发

官网开源代码 Documentation - MiCode/Xiaomi_Kernel_OpenSource - Sourcegraph Xiaomi 11T Pro GitHub - MiCode/Xiaomi_Kernel_OpenSource: Xiaomi Mobile Phone Kernel OpenSourceAndroid 开源项目 | Android Open Source Project google安卓官网 目录概览 参考…

vs 启动调用的目标发生异常_如何解决不可测、异常场景的问题?

阿里QA导读:在软件研发过程中,发布前跨多个系统的联调测试是不可或缺的一环,而在联调过程中,经常会遇到一些比较棘手的困难,阻塞整个联调进程。其中比较典型的有:第三方的研发节奏不一致,导致无…

Linux内核 scatterlist介绍

scatterlist 物理内存的散列表。通俗讲,就是把一些分散的物理内存,以列表的形式组织起来 诞生背景 假设有三个模块可以访问memory:CPU、DMA控制器和某个外设。CPU通过MMU以虚拟地址(VA)的形式访问memory;…

Linux内核 crypto文件夹 密码学知识学习

密码算法分类 对称算法非对称算法消息摘要(单向哈希)算法这些算法作为加密函数框架的最底层,提供加密和解密的实际操作。这些函数可以在内核crypto文件夹下,相应的文件中找到。不过内核模块不能直接调用这些函数,因为…

Linux crypto相关知识的汇总 Linux加密框架crypto中的算法和算法模式(一)

Linux加密框架中的算法和算法模式 Linux加密框架中的算法和算法模式(一)_家有一希的博客-CSDN博客 加密框架支持的密码算法主要是对称密码算法和哈希算法,暂时不支持非对称密码算法。除密码算法外,加密框架还包括伪随机数生成算法…

Linux crypto相关知识的汇总 Linux加密框架crypto对称算法和哈希算法加密模式

参考链接 Linux加密框架中的算法和算法模式(二)_家有一希的博客-CSDN博客 对称算法 分组算法模式 ECB模式 ECB模式下,明文数据被分为大小合适的分组,然后对每个分组独立进行加密或解密如下图所示如果两个明文块相同&#xff0c…

Linux加密框架中的算法和算法模式

参考链接 Linux加密框架中的算法和算法模式(三)_家有一希的博客-CSDN博客 对称算法 14 如上所示,在arc4.c中定义了两个与RC4算法相关的算法实现,分别为arc4和ecb(arc4),其中arc4是RC算法的算法实现,而ecb…

Linux加密框架crypto AES代码相关

例子 aes_generic.c - crypto/aes_generic.c - Linux source code (v5.15.11) - Bootlin static struct crypto_alg aes_alg {.cra_name "aes",.cra_driver_name "aes-generic",.cra_priority 100,.cra_flags CRYPTO_ALG_TYPE_CIPHER,.cra_blocks…

Linux加密框架 crypto RC4

参考链接 arc4.h Linux加密框架中的主要数据结构(一)_家有一希的博客-CSDN博客 头文件 arc4.h - include/crypto/arc4.h - Linux source code (v5.15.11) - Bootlin实现代码 arc4.c arc4.c - crypto/arc4.c - Linux source code (v5.15.11) - Bootlin…

Linux加密框架 crypto 哈希算法说明 同步哈希shash_alg | 异步哈希 ahash_alg | 通用部分抽象 hash_alg_common

参考链接 Linux加密框架中的主要数据结构(二)_家有一希的博客-CSDN博客 定义 通用算法说明数据结构crypto_alg的联合体成员变量cra_u中包含多种算法的个性化属性,如分组算法、块加密算法、压缩算法、伪随机数算法等,但不包含哈希…

Linux加密框架 crypto 哈希算法举例 MD5

参考链接 Linux加密框架 crypto 哈希算法说明 同步哈希shash_alg | 异步哈希 ahash_alg | 通用部分抽象 hash_alg_common_CHYabc123456hh的博客-CSDN博客Linux加密框架中的主要数据结构(二)_家有一希的博客-CSDN博客 MD5 md5.h - include/crypto/md5.h …

事务没提交的数据查的出来吗?_“金三银四”面试官:说说事务的ACID,什么是脏读、幻读?...

一、事务事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。--摘自百科在MySQL里,事务是在引擎层面实现,比如MyIsam不支持,InnoDB支持面试清单(Java岗):JavaJVM数…

Linux加密框架 crypto 算法模板

参考链接 Linux加密框架中的主要数据结构(三)_家有一希的博客-CSDN博客algapi.h - include/crypto/algapi.h - Linux source code (v5.15.11) - Bootlin 定义 struct crypto_template {struct list_head list;struct hlist_head instances;struct modu…

Linux加密框架 crypto 算法模板 CBC模板举例

参考链接 Linux加密框架中的主要数据结构(三)_家有一希的博客-CSDN博客https://blog.csdn.net/CHYabc123456hh/article/details/122194754 CBC算法模板 cbc.c - crypto/cbc.c - Linux source code (v5.15.11) - BootlinCBC算法模板属性 1)CBC算法模板名…

leetcode数组汇总_LeetCode刷题实战43:字符串相乘

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !今天和大家…