大内高手常见内存错误Word文件下载.docx

上传人:b****6 文档编号:19274869 上传时间:2023-01-05 格式:DOCX 页数:19 大小:37.84KB
下载 相关 举报
大内高手常见内存错误Word文件下载.docx_第1页
第1页 / 共19页
大内高手常见内存错误Word文件下载.docx_第2页
第2页 / 共19页
大内高手常见内存错误Word文件下载.docx_第3页
第3页 / 共19页
大内高手常见内存错误Word文件下载.docx_第4页
第4页 / 共19页
大内高手常见内存错误Word文件下载.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

大内高手常见内存错误Word文件下载.docx

《大内高手常见内存错误Word文件下载.docx》由会员分享,可在线阅读,更多相关《大内高手常见内存错误Word文件下载.docx(19页珍藏版)》请在冰豆网上搜索。

大内高手常见内存错误Word文件下载.docx

你会说p指向的内存被释放了。

没错,p本身有变化吗?

答案是p本身没有变化。

它指向的内存仍然是有效的,你继续读写p指向的内存,没有人能拦得住你。

释放掉的内存会被内存管理器重新分配,此时,野指针指向的内存已经被赋予新的意义。

对野指针指向内存的访问,无论是有意还是无意的,都为此会付出巨大代价,因为它造成的后果,如同越界访问一样是不可预料的。

释放内存后立即把对应指针置为空值,这是避免野指针常用的方法。

这个方法简单有效,只是要注意,当然指针是从函数外层传入的时,在函数内把指针置为空值,对外层的指针没有影响。

比如,你在析构函数里把this指针置为空值,没有任何效果,这时应该在函数外层把指针置为空值。

4. 

访问空指针。

空指针在C/C++中占有特殊的地址,通常用来判断一个指针的有效性。

空指针一般定义为0。

现代操作系统都会保留从0开始的一块内存,至于这块内存有多大,视不同的操作系统而定。

一旦程序试图访问这块内存,系统就会触发一个异常。

操作系统为什么要保留一块内存,而不是仅仅保留一个字节的内存呢?

原因是:

一般内存管理都是按页进行管理的,无法单纯保留一个字节,至少要保留一个页面。

保留一块内存也有额外的好处,可以检查诸如p=NULL;

p[1]之类的内存错误。

在一些嵌入式系统(如arm7)中,从0开始的一块内存是用来安装中断向量的,没有MMU的保护,直接访问这块内存好像不会引发异常。

不过这块内存是代码段的,不是程序中有效的变量地址,所以用空指针来判断指针的有效性仍然可行。

在访问指针指向的内存时,在确保指针不是空指针。

访问空指针指向的内存,通常会导致程度崩溃,或者不可预料的错误。

5. 

引用未初始化的变量。

未初始化变量的内容是随机的(像VC一类的编译器会把它们初始化为固定值,如0xcc),使用这些数据会造成不可预料的后果,调试这样的BUG也是非常困难的。

对于态度严谨的程度员来说,防止这类BUG非常容易。

在声明变量时就对它进行初始化,是一个编程的好习惯。

另外也要重视编译器的警告信息,发现有引用未初始化的变量,立即修改过来。

6. 

不清楚指针运算。

对于一些新手来说,指针常常让他们犯糊涂。

比如int*p=…;

p+1等于(size_t)p+1吗

老手自然清楚,新手可能就搞不清了。

事实上,p+n 

等于 

(size_t)p+n*sizeof(*p)

指针是C/C++中最有力的武器,功能非常强大,无论是变量指针还是函数指针,都应该掌握都非常熟练。

只要有不确定的地方,马上写个小程序验证一下。

对每一个细节都了然于胸,在编程时会省下不少时间。

7. 

结构的成员顺序变化引发的错误。

在初始化一个结构时,老手可能很少像新手那样老老实实的,一个成员一个成员的为结构初始化,而是采用快捷方式,如:

Struct 

s

{

int 

l;

char* 

p;

};

main(int 

argc, 

argv[])

struct 

s1 

={4, 

"

abcd"

return 

0;

}

以上这种方式是非常危险的,原因在于你对结构的内存布局作了假设。

如果这个结构是第三方提供的,他很可能调整结构中成员的相对位置。

而这样的调整往往不会在文档中说明,你自然很少去关注。

如果调整的两个成员具有相同数据类型,编译时不会有任何警告,而程序的逻辑上可能相距十万八千里了。

正确的初始化方法应该是(当然,一个成员一个成员的初始化也行):

={.l=4,.p 

s2 

={l:

4, 

p:

8. 

结构的大小变化引发的错误。

我们看看下面这个例子:

base

n;

base 

b;

m;

在OOP中,我们可以认为第二个结构继承了第一结构,这有什么问题吗?

当然没有,这是C语言中实现继承的基本手法。

现在假设第一个结构是第三方提供的,第二个结构是你自己的。

第三方提供的库是以DLL方式分发的,DLL最大好处在于可以独立替换。

但随着软件的进化,问题可能就来了。

当第三方在第一个结构中增加了一个新的成员intk;

,编译好后把DLL给你,你直接给了客户了。

程序加载时不会有任何问题,在运行逻辑可能完全改变!

原因是两个结构的内存布局重叠了。

解决这类错误的唯一办法就是全部重新相关的代码。

解决这类错误的唯一办法就是重新编译全部代码。

由此看来,DLL并不见得可以动态替换,如果你想了解更多相关内容,建议阅读《COM本质论》。

9. 

分配/释放不配对。

大家都知道malloc要和free配对使用,new要和delete/delete[]配对使用,重载了类new操作,应该同时重载类的delete/delete[]操作。

这些都是书上反复强调过的,除非当时晕了头,一般不会犯这样的低级错误。

而有时候我们却被蒙在鼓里,两个代码看起来都是调用的free函数,实际上却调用了不同的实现。

比如在Win32下,调试版与发布版,单线程与多线程是不同的运行时库,不同的运行时库使用的是不同的内存管理器。

一不小心链接错了库,那你就麻烦了。

程序可能动则崩溃,原因在于在一个内存管理器中分配的内存,在另外一个内存管理器中释放时出现了问题。

10. 

返回指向临时变量的指针

大家都知道,栈里面的变量都是临时的。

当前函数执行完成时,相关的临时变量和参数都被清除了。

不能把指向这些临时变量的指针返回给调用者,这样的指针指向的数据是随机的,会给程序造成不可预料的后果。

下面是个错误的例子:

char*get_str(void)

char 

str[]={"

str;

char*argv[])

=get_str();

printf("

%s\n"

 

p);

下面这个例子没有问题,大家知道为什么吗?

get_str(void)

str 

={"

get_str();

11. 

试图修改常量

在函数参数前加上const修饰符,只是给编译器做类型检查用的,编译器禁止修改这样的变量。

但这并不是强制的,你完全可以用强制类型转换绕过去,一般也不会出什么错。

而全局常量和字符串,用强制类型转换绕过去,运行时仍然会出错。

原因在于它们是是放在.rodata里面的,而.rodata内存页面是不能修改的。

试图对它们修改,会引发内存错误。

下面这个程序在运行时会出错:

;

*p 

'

1'

12. 

误解传值与传引用

在C/C++中,参数默认传递方式是传值的,即在参数入栈时被拷贝一份。

在函数里修改这些参数,不会影响外面的调用者。

如:

#include<

stdlib.h>

stdio.h>

voidget_str(char*p)

p=malloc(sizeof("

));

strcpy(p,"

);

return;

intmain(intargc,char*argv[])

char*p=NULL;

get_str(p);

p=%p\n"

p);

return0;

在main函数里,p的值仍然是空值。

13. 

重名符号。

无论是函数名还是变量名,如果在不同的作用范围内重名,自然没有问题。

但如果两个符号的作用域有交集,如全局变量和局部变量,全局变量与全局变量之间,重名的现象一定要坚决避免。

gcc有一些隐式规则来决定处理同名变量的方式,编译时可能没有任何警告和错误,但结果通常并非你所期望的。

下面例子编译时就没有警告:

t.c

#include 

<

count 

=0;

get_count(void)

count;

main.c

externintget_count(void);

intcount;

count=10;

get_count=%d\n"

get_count());

如果把main.c中的intcount;

修改为intcount=0;

,gcc就会编辑出错,说multipledefinitionof`count'

它的隐式规则比较奇妙吧,所以还是不要依赖它为好。

14. 

栈溢出。

我们在前面关于堆栈的一节讲过,在PC上,普通线程的栈空间也有十几M,通常够用了,定义大一点的临时变量不会有什么问题。

而在一些嵌入式中,线程的栈空间可能只5K大小,甚至小到只有256个字节。

在这样的平台中,栈溢出是最常用的错误之一。

在编程时应该清楚自己平台的限制,避免栈溢出的可能。

15. 

误用sizeof。

尽管C/C++通常是按值传递参数,而数组则是例外,在传递数组参数时,数组退化为指针(即按引用传递),用sizeof是无法取得数组的大小的。

从下面这个例子可以看出:

void 

test(char 

str[20])

%s:

size=%d\n"

__func__, 

sizeof(str));

str[20] 

={0};

test(str);

[root@localhostmm]#./t.exe

test:

size=4

main:

size=20

16. 

字节对齐。

字节对齐主要目的是提高内存访问的效率。

但在有的平台(如arm7)上,就不光是效率问题了,如果不对齐,得到的数据是错误的。

所幸的是,大多数情况下,编译会保证全局变量和临时变量按正确的方式对齐。

内存管理器会保证动态内存按正确的方式对齐。

要注意的是,在不同类型的变量之间转换时要小心,如把char*强制转换为int*时,要格外小心。

另外,字节对齐也会造成结构大小的变化,在程序内部用sizeof来取得结构的大小,这就足够了。

若数据要在不同的机器间传递时,在通信协议中要规定对齐的方式,避免对齐方式不一致引发的问题。

17. 

字节顺序。

字节顺序历来是设计跨平台软件时头疼的问题。

字节顺序是关于数据在物理内存中的布局的问题,最常见的字节顺序有两种:

大端模式与小端模式。

大端模式是高位字节数据存放在低地址处,低位字节数据存放在高地址处。

小端模式指低位字节数据存放在内存低地址处,高位字节数据存放在内存高地址处;

比如longn=0x11223344。

模式

第1个字节

第2个字节

第3个字节

第4个字节

大端模式

0x11

0x22

0x33

0x44

小端模式

在普通软件中,字节顺序问题并不引人注目。

而在开发与网络通信和数据交换有关的软件时,字节顺序问题就要特殊注意了。

18. 

多线程共享变量没有用valotile修饰。

在关于全局内存的一节中,我们讲了valotile的作用,它告诉编译器,不要把变量优化到寄存器中。

在开发多线程并发的软件时,如果这些线程共享一些全局变量,这些全局变量最好用valotile修饰。

这样可以避免因为编译器优化而引起的错误,这样的错误非常难查。

可能还有其它一些内存相关错误,一时想不全面,这里算是抛砖引玉吧,希望各位高手补充。

~~end~~

转载时请注明出处和作者联系方式:

作者联系方式:

李先静 

xianjimliathotmaildotcom>

更新时间:

2007-7-9

发表于@2006年07月12日 07:

59:

00 

评论( 

26 

) 

举报| 

收藏

绗戠瑧鐢?

Url= 

发表于WedJul12200608:

44:

00GMT+0800(ChinaStandardTime) 

举报回复

总结的很好啊

Bennie 

发表于WedJul12200609:

15:

#13 

这个规则并不仅仅是GCC的隐式规则,而是C的。

32:

所以,对于C的全局变量,要注意其默认为extern,而显式的使用static和extern来控制文件(编译单元)作用域。

而那个赋值的地方,我以为使用配对的set_count更好。

7116589 

发表于WedJul12200610:

27:

谢谢,关注ing

drzhouweiming

发表于WedJul12200612:

10:

#2 

内存越界问题,补充一下,还有变量强制类型转换有时会导致读写越界。

TY677879 

发表于WedJul12200617:

31:

写得好!

在此借贵宝地揭露一下太极语言这呆B诈骗犯的无耻嘴脸!

universe(它自己鬼称"

太极语言"

)你个疯狗,又在CSDN瞎叫唤了,用你的几吧太极语言,不如去网上down一个最新版的"

太骗语言"

而且一分钱也不花!

你这个呆B只是刻录了一个很低很低版本"

在光盘上,打磨上一个"

universe"

字样,就恬不知耻地拿出去卖钱,当成自己开发的一样,还要卖RMB80块,真不要脸!

!

如果你只卖2块钱的光盘介质费,而且老老实实的将光盘上的字打磨成"

universe盗版太骗语言"

我还可以考虑买一张来让你有钱住进疯人院!

可惜你他妈的太不诚实了,没办法,我只有在这戳穿你了!

你们还真以为太极语言这傻_B有个叫太极计算机的皮包公司?

这全是它自导自演的一出闹剧,它哪有啥狗屁公司哦,加上它当妓女的老婆也才俩疯狗:

太极语言你这傻_B还阴魂不散啊?

看看CSDN众多网友对你的评论吧!

你已触犯众怒,你在与全体人民为敌!

告诉你,人民群众的眼睛是雪亮的!

太极语言你不仅人做不了,连做狗都失败了!

野狗->

疯狗->

死狗,就是你的路线图!

你已死到临头,要想不被消灭,只有两条路:

去疯人院,还是劳改农场?

absurd

发表于WedJul12200621:

21:

toBennie和drzhouweiming:

谢谢补充。

xsxinguang 

发表于ThuJul13200620:

14:

谢谢!

写的很好

flyingxu 

发表于FriJul14200621:

46:

7:

structss1={.l=4,.p="

structss2={l:

4,p:

怎么编译啊?

我在想是不是一定要gcc才行啊 

能解释下10吗?

11:

我在windows中也没有发现运行中出错。

我觉得“abcd”是const的,但是操作系统这么智能,能在运行中知道这块内存是const,那真的蛮强大的。

我没有在别的系统中试过哦,如果真能出错,那这个操作系统是比windows强的,我觉得。

发表于SunJul16200609:

05:

toflyingxu:

据说第一种C99的标准,第二是gcc的扩展,在VC6上试了一下,两者都不支持,只能一个一个的赋值。

谢谢你的提醒。

关于10:

前者是数组,常量被拷贝到变量了,后者是指针,没有拷贝。

关于11:

谢谢你的测试,我只在linux上测试过,程序会出现Segmentationfault。

发表于MonJul17200610:

25:

10和11其实牵涉到C语言中为了兼容而作的一个转换,"

的类型是constchar[5],它可以自然的转换到constchar*,但是因为最早"

的类型是char[5],为了大量的旧代码兼容,标准允许了转换为char*,但是这是一个受限转换,只有在特定环境下可以转换,而初始化一个char*就是其中一个。

但是最好还是不要使用这个受限转换。

发表于MonJul17200621:

58:

toBennie:

谢谢补充

tjww 

发表于MonAug14200623:

55:

有一个隐蔽的错误是文件句柄错误,A线程打开一个文件,得到句柄AA,然后A线程关闭句柄AA,接着B线程打开一个文件,得到句柄BB,此时BB的句柄恰恰就是AA,当线程A接着错误地再次关闭句柄AA的时候,并不是无效操作,而是错误地关闭了线程B打开的句柄BB,此时如果线程C再打开句柄CC,则BB和CC实际上指向同一个句柄,OK,错误出现了,这种句柄开关的错误,更加隐蔽,但是一样可以用计数器的方式避免,计数器是个好东西呀 

注:

以上分析依赖于系统分配句柄的方式,目前linux和win32都是从最小数字开始分配的

发表于TueAug15200621:

47:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1