错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误
题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。
对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两种变量类型具有雷同的size
for(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
错误和内存
---------------------------------
错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误的更多相关文章
- 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
- 《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才 ...
- C程序中常见的内存操作错误
对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...
- 对开发中常见的内存泄露,GDI泄露进行检测
对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...
- Android中常见的内存泄漏
为什么会产生内存泄漏? 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. ...
- JavaScript 工作原理之三-内存管理及如何处理 4 类常见的内存泄漏问题(译)
原文请查阅这里,本文有进行删减,文后增了些经验总结. 本系列持续更新中,Github 地址请查阅这里. 这是 JavaScript 工作原理的第三章. 我们将会讨论日常使用中另一个被开发者越来越忽略的 ...
- JavaScript中常见的数组操作函数及用法
JavaScript中常见的数组操作函数及用法 昨天写了个帖子,汇总了下常见的JavaScript中的字符串操作函数及用法.今天正好有时间,也去把JavaScript中常见的数组操作函数及用法总结一下 ...
- JavaScript中常见的字符串操作函数及用法
JavaScript中常见的字符串操作函数及用法 最近几次参加前端实习生招聘的笔试,发现很多笔试题都会考到字符串的处理,比方说去哪儿网笔试题.淘宝的笔试题等.如果你经常参加笔试或者也是一个过来人,相信 ...
- 用windbg+sos找出程序中谁占用内存过高,谁占用CPU过高(转载)
原文地址: http://www.cnblogs.com/Lawson/archive/2011/01/23/1942692.html 很早看到windbg+sos方面的知识,一直没仔细学习,也许因为 ...
随机推荐
- android和ios流媒体库推荐
1基本信息编辑 Vitamio是一款 Android 与 iOS 平台上的全能多媒体开发框架,全面支持硬件解码与 GPU 渲染.从2011年8月上线到2014年1月,Vitamio 凭借其简洁易用的 ...
- PagerSlidingTabStrip 高亮选中标题
1.选中标题后,高亮标题@Override public void onPageSelected(int position) { setSelectTextColor(position); if (d ...
- linux命令——rmdir
rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的.rm - r dir命令可代替rmdir rmdir [选项]... 目录... - p 递归删除目录dirname,当 ...
- MATLAB常用数据类型的转换
一直以来,在使用MATLAB进行运算的过程中,经常会用到对图像的各种处理,因此需要对图像进行数据转换,因此对经常用到的转换进行整理,方便查看. 1.uint8转化为double用来方便图像的运算: d ...
- void、void*以及NULL
void.void*以及NULL 写在前面 在使用C++的过程中,void和NULL用到的频率挺高的,但是从来没有去探索过这两个关键字的联系和区别,也没有对它们做更多的探索.对于void*,说实话,实 ...
- CSS_网站配色参考方案
http://www.cnblogs.com/QLeelulu/archive/2008/04/04/1136974.html Shiny silver [#EEEEEE] Reddi ...
- Xcode 6 越狱开发基础
最近接触到XCode越狱开发的问题,越狱开发首先iphone设备得越狱,然后安装Appsync,安装之后,安装ipa将不再验证程序签名的有效性,不签名的程序也可以直接在设备上运行,只需要保证IPA本身 ...
- 【Hadoop学习】Super用户以其他用户的名义执行操作
Hadoop版本:2.6.0 本文系从官方文档翻译而来,转载请尊重译者的工作,注明以下链接: http://www.cnblogs.com/zhangningbo/p/4146410.html 简介 ...
- PC问题-可以PING通IP,PING名字不通,可以远程,但不能访问共享文件夹?
问题现象:可以PING通IP,PING名字不通,可以远程,但不能访问共享文件夹? 问题原因:目标主机中NetLogon服务未启动. 问题处理:远程打开目标主机,“我的电脑-管理-服务-启动(改为自动) ...
- java assert的用法简介
assert的基本用法 assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制,如C,C++和Eiffel等,但是支持的形式不尽相同,有的是通过语言本身.有的是通过库 ...