题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的。因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离以后才表现出来。头几天线上模块因堆内存写越界1个字节引起各种诡异崩溃,定位问题过程当中的折腾仍历历在目,今天读到《深刻理解计算机系统》第9章-虚拟存储器,发明书中总结了C程序中常见的内存操纵有关的10种典型编程错误,总结的比拟全面。故作为笔记,记载于此。

1. 间接引用无效指针
        进程虚拟地址空间的某些地址范围可能没有映射到任何有意义的数据,如果我们试图间接引用一个指向这些地址的指针,则操纵系统会以Segment Fault终止进程。而且,虚拟存储器的某些区域是只读的(如.text或.rodata),试图写这些区域会以掩护异常中止当前进程。
        如从stdin读取一个int变量时,scanf("%d", &val)是准确用法,若误写为scanf("%d", val)时,val的值会被解释为一个地址,并试图向该地址写数据。在最好的情况下,进程立即异常中止。在最坏的情况下,val的值恰好对应于虚拟存储器的某个正当的具有读/写权限的内存区域,于是该内存单元会被改写,而这通常会在相当长的一段时间后形成灾难性的、令人困惑的后果。

2. 读未初始化的存储器
        C语言的malloc并不负责初始化申请到的内存区域,因此,常见的错误是假设堆存储器被初始化为0,例如:

int * foo(int **A, int *x, int n){int i, j;int * y = (int *)Malloc(n * sizeof(int));for(i = 0; i < n; i++) {for(j = 0; j < n; j++){y[i] += A[i][j] * x[j];}}return y;}

上述代码中,错误地假设了y被初始化为0。准确的实现方式是显式将y[i]置为0或者应用calloc。

3. 栈缓冲区溢出
         例如:

char buf[5];sprintf(buf, "%s", "hello world");

上面的代码致使栈缓冲区溢出,安全的做法是:1)根据需求定义适合的buffer;2)采取snprintf(buf, sizeof(buf), "%s", "hello world")来实时截断。

4. 误以为指针与其指向的对象是雷同巨细的
        例如:

int **makeArray(int n, int m){int i;int **A = (int **)Malloc(n*sizeof(int));   // 这里错误地以为int *与int两种变量类型具有雷同的sizefor(i = 0; i < n; i++) {A[i] = (int *)Malloc(m * sizeof(int));}return A;}

上述代码目的是创立一个由n个指针构成的数组,每一个指针均指向一个包含m个int的数组,但误将sizeof(int *)写成sizeof(int)。这段代码只有在int和int *的size雷同的机器上运行良好。如果在像Core i7这样的机器上运行这段代码,由于指针变量的size大于sizeof(int),则会引发代码中的for循环写越界。因为这些字中的一个很多是已分配块的边界标记脚部,所以我们可能不会立即发明这个错误,直到进程运行很久释放这个内存块时,此时,分配器中的合并代码会戏剧性地失败,而没有任何明显的原因。这是"在远处起作用"(action at distance)的一个隐秘示例,这类"在远处起作用"是与存储器有关的编程错误的典型情况。

5. 形成错位错误
         错位(Off-by-one)错误是另一种常见的覆盖错误来源:

每日一道理
全部世界,因为有了阳光,城市有了生机;细小心灵,因为有了阳光,内心有了舒畅。明媚的金黄色,树丛间小影成像在叶片上泛有的点点破碎似的金灿,海面上直射反映留有的随波浪层层翻滚的碎片,为这大自然创造了美景,惹人醉的温馨之感,浓浓暖意中夹杂着的明朗与柔情,让雨过天晴后久违阳光的心灵重新得到了滋润!
int ** makeArray(int n, int m){int i;int **A = (int **)Malloc(n * sizeof(int *));for(i = 0; i <= n; i++) {A[i] = (int *)Malloc(m * sizeof(int));}return A;}

很明显,for循环次数分歧预期,致使写越界。荣幸的话,进程会立即崩溃;不幸的话,运行很长时间才抛出各种诡异问题。

6. 引用指针,而不是它所指向的对象
        如果不注意C操纵符的优先级和结合性,就会错误地操纵指针,而不是指针所指向的对象。
        比如上面的函数,其目的是删除一个有*size项的二叉堆里的第一项,然后对剩下的*size-1项重建堆:

int * binheapDelete(int **binheap, int *size){int *packet = binheap[0];binheap[0] = binheap[*size - 1];*size--;  // 此处应该为(*size)--heapify(binheap, *size, 0);return (packet);}

上述代码中,由于--和*优先级雷同,从右向左结合,所以*size--其实增加的是指针自己的值,而非其指向的整数的值。因此,服膺:当你对优先级和结合性有疑问时,就应该应用括号。

7. 误解指针运算
        在C/C++中,指针的算术操纵是以它们指向的对象的巨细为单位来进行的。例如上面函数的功能是扫描一个int的数组,并返回一个指针,指向val的初次出现:

int * search(int *p, int val){while(*p && *p != val) {p += sizeof(int); // 此处应该为p++,否则p += 4会致使大部分元素被跳过}}

8. 引用不存在的变量

C/C++新手不理解栈的规矩时,可能会引用不再正当的当地变量,例如:

int * stackref(){int val;return &val;}

函数返回的指针(假设为p)指向栈中的局部变量,但该变量在函数返回后随着stackref栈帧的销毁已经不再有效。也即:尽管函数返回的指针p仍然指向一个正当的存储器地址,但它已经不再指向一个正当的变量了。当程序后续调用其它函数时,存储器将重用刚才销毁栈帧处的存储器区域。再后来,如果程序分配某个值给*p,那么它可能实际上正在修改另一个函数栈帧中的数据,从而潜在地带来灾难性的、令人困惑的后果。

9. 引用闲暇堆块中的数据
        典型的错误为:引用已经被释放了的堆块中的数据,例如:

int * heapref(int n, int m){int i;int *x, *y;x = (int *)Malloc(n * sizeof(int));/*  各种操纵 */free(x);y = (int *)Malloc(m * sizeof(int));for(i = 0; i < m; i++) {y[i] = x[i]++;  // 此处的x之前已经被释放了!}}

10. 内存泄露
       内存泄露是迟缓、隐性的杀手,当程序员忘记释放已分配块时会产生这类问题,例如:

void leak(int n){int *x = (int *)Malloc(n * sizeof(int));return;}

如果leak在程序全部生命周期内只调用数次,则问题还不是很严峻(但还是会浪费存储器空间),因为随着进程结束,操纵系统会回收这些内存空间。但如果leak()被经常调用,那就会产生严峻的内存泄露,最坏的情况下,会占用全部虚拟地址空间。对于像守护进程和服务器这样的程序来讲,内存泄露是严峻的bug,必须加以看重。

【参考资料】
《深刻理解计算机系统》第9章 — 虚拟存储器

============== EOF ==================

文章结束给大家分享下程序员的一些笑话语录: 一个合格的程序员是不会写出 诸如 “摧毁地球” 这样的程序的,他们会写一个函数叫 “摧毁行星”而把地球当一个参数传进去。

--------------------------------- 原创文章 By
错误和内存
---------------------------------

转载于:https://www.cnblogs.com/xinyuyuanm/p/3150400.html

错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误相关推荐

  1. C程序中常见的内存操作错误

    对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...

  2. 孙卫琴的《精通JPA与Hibernate》的读书笔记:在程序中动态指定立即左外连接检索

    在持久化类中通过注解设定的检索策略是固定的,要么为延迟检索,要么为立即检索.但应用逻辑是多种多样的,有些情况下需要延迟检索,而有些情况下需要立即检索. Hibernate允许在应用程序中覆盖持久化类中 ...

  3. 【读书笔记】程序员的自我修养总结(七)

    [读书笔记]程序员的自我修养总结(七) 标签: [编程开发] 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:这是程序员的自我修养一书的读书总结,随着阅 ...

  4. 《C Primer Plus》读书笔记——存储类、链接和内存管理

    背景 距离上次写读书笔记的日子已有半个月了.这段时间一直在做摄像头直立平衡车,也把<C Primer Plus>的中级部分扫了一遍.现在做赛道算法识别遇到瓶颈了,就想把读书笔记补回来.原计 ...

  5. 读书笔记:程序员的数学 概率统计

    读书笔记:程序员的数学 概率统计 特点 内容 第一.二章 概率定义 多随机变量 第三.四章 离散.连续分布 第五章 协方差矩阵与多元正态分布 第六.七章 估计与检验 伪随机数 第八章 各类应用 体会 ...

  6. Android中常见的内存泄露

    内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏.内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会 ...

  7. 【读书笔记 | 自动驾驶中的雷达信号处理(第7章 目标滤波与跟踪)】

    本文编辑:调皮哥的小助理 大家好,又和大家见面了,时间过得很快,到目前为止,如下面的目录所示,我们已经阅读过汽车雷达目标检测的一些基本的原理了,特别是距离.速度和角度.虽然这些表示瞬时目标状态的信息可 ...

  8. 读书笔记 | 自动驾驶中的雷达信号处理(第2章 雷达方程)

    本文编辑:调皮哥的小助理 2.1 介绍 本文主要介绍雷达方程,这有助于理解传播损耗对雷达发射信号的影响.本期的内容都很简单,通俗易懂,即使有存在不易理解的,我会额外加以注释,总而言之,会站在一个初学者 ...

  9. boost::timer模块检查在同一程序中使用 Chrono 和 Timer 不会导致链接错误

    boost::timer模块检查在同一程序中使用 Chrono 和 Timer 不会导致链接错误 实现功能 C++实现代码 实现功能 boost::timer模块检查在同一程序中使用 Chrono 和 ...

最新文章

  1. 犹太人的思维习惯 (转载)
  2. 堆溢出DWORD SHOOT原理
  3. 《It's All Upside Down》作者访谈录
  4. WF,WPF,Silverlight的DependencyProperty 附加属性
  5. 4.2.1 OS之磁盘的结构(磁盘、磁道、扇区、盘面、柱面、磁头)
  6. android app自动更新界面_Android自定义view之模仿登录界面文本输入框(华为云APP)...
  7. c语言素数程序出现大空行,C语言实现寻找大素数
  8. android闹钟详细设计,基于LabVIEW的闹钟设计 详细文档+程序
  9. 勇探计算机城堡教学反思,神秘的城堡教学反思
  10. nodejs应用在linux服务器中的部署
  11. MAC地址了解(根据设备MAC地址查询生产厂商信息)
  12. 关于Protel 2004 绘制电路原理图
  13. 机器学习中的数据不平衡解决方案大全
  14. 《蜥蜴脑法则》读后感
  15. 方腔驱动流的simple算法(附matlab与c++程序)
  16. Unity—Json1
  17. log4cpp 的使用
  18. 【工利其器】必会工具之 Source Insight篇
  19. 基于SSM的游戏账号受理平台
  20. python股票指标计算库_GitHub - unclevicky/stock: stock,股票系统。使用python进行开发。...

热门文章

  1. Java学习笔记_数组
  2. Visual Studio:error MSB8020
  3. iOS上文本处理之简史
  4. windows下buildbot 的搭建及config文件讲解
  5. DCASE三次挑战赛概览
  6. 空间谱专题02:波束形成(Beamforming)
  7. 《觉建筑》《斑点狗眼里的世界》首发式
  8. 新体验小说:作家重新卷入当代历史的一种方式——纪念“新体验小说”倡导一周...
  9. 压力测试过负载均衡_性能测试的方法有哪些?
  10. u盘 linux centos 5.3,鸟哥linux私房菜学习笔记,U盘安装centos5.3不能正常进入图形界面的问题...