内存故障问题

只需一步,快速开始
一步搞定
扫一扫,访问微社区
后使用快捷导航没有帐号?
查看: 16209|回复: 16
帖内搜作者:
威望1600 金钱2925 帖子精华0好友注册时间最后登录
白银会员, 积分 1600, 距离下一级还需 400 积分
当前用户组为 白银会员当前积分为 1600, 升到下一级还需要 400 点。
本帖最后由 山东红圈 于
20:10 编辑
我安装 的CC使用中出现的问题:一是在PS中处理十余张较大图片后,提示内存不足,即将关闭,其实我的电脑是I7版本,6G内存,并为PS分配了5G。二是在使用“剪裁”和“调整图层”后,背景层自动解锁,变为普通图层。三是CAMERA RAW版本为8.0,不能升级到8.2。请红圈老师和行家指教。
威望15670 金钱25830 帖子精华0好友居住地山东省 滨州市注册时间最后登录
本帖最后由 山东红圈 于
22:59 编辑
先说说第一个问题:
在 Photoshop的使用中,经常遇到提示内存不足及暂存盘已满等问题,请按照下面的思路调整一下看看是否见效:
& && & 1、设定系统虚拟内存(Win7)
  用右键单击“计算机”,依次点“属性”—“高级系统设置”—&高级&—“性能、设置”,在“性能选项”中选“高级、虚拟内存”点“更改”,去掉“自动管理所有驱动器的分页文件大小”,指定虚拟内存的硬盘,如c盘,在“自定义大小”下面将“初始大小”和“最大值”都设置为你物理内存的2-2.5倍,你的内存是6G,设置为12000M就可以了。然后点确定——重启生效。见图一。
15:11 上传
  2、设置PSCC 虚拟磁盘
  在Photoshop菜单中依次点击“编辑—首选项—性能”,在“暂存盘”栏中设定Photoshop的虚拟磁盘盘符,第一暂存盘中的设定应注意不可和Windows虚拟内存的磁盘盘符一致。Photoshop第一暂存盘的可用空间一定要大,还应时常用磁盘碎片整理程序整理硬盘。 第一第二暂存盘的顺序可用选框右边的上下箭头调整。 (见图二)
& && & 3、设置PSCC 缓存级别 & && & 在“历史记录与高速缓存”中设定Photoshop使用的高速缓存级数,级数越高Photoshop运行的速度就越快。“高速缓存级别”一般默认为4,将其改成8,“高速缓存拼贴大小”改成1024,这样可以充分获得最佳的显卡GPU性能。 (见图二)
& && & 4、设置PSCC 内存使用
& && & 在“内存使用情况”下面,用调整条将数值数设为80%,即把80%左右的物理内存供给Photoshop使用。
(见图二)&&
22:58 上传
  5、开机时尽量不要加载过多程序
  用360安全卫士等系统优化软件,将不必要的启动项去掉,以提高系统的运行速度。
& &&&关于你的第二个问题我没找到原因,有待慢慢研究。
& && & 第三个问题:& && & 破解版的软件大都不能自动升级,需要自己下载后升级。请看我的这个帖子:
红圈、金圈都是好圈。
威望1600 金钱2925 帖子精华0好友注册时间最后登录
白银会员, 积分 1600, 距离下一级还需 400 积分
当前用户组为 白银会员当前积分为 1600, 升到下一级还需要 400 点。
本帖最后由 西峰旧雨 于
10:14 编辑
衷心谢谢红圈老师不厌其详的指导,几年来你一直是这样热心帮助他人。您给的链接我早就看了并下载,但提示说不能安装,能否再说一声如何手动安装?是不是不能装在CC中?
没有华丽的言辞,就一句“好人一生平安”吧!
威望15670 金钱25830 帖子精华0好友居住地山东省 滨州市注册时间最后登录
本帖最后由 山东红圈 于
10:36 编辑
西峰旧雨 发表于
衷心谢谢红圈老师不厌其详的指导,几年来你一直是这样热心帮助他人。您给的链接我早就看了并下载,但提示说 ...
更新方法:
方法1、在线更新:打开PhotoshopCC,点帮助——更新。如果你的PhotoshopCC中更新不可用,可打开Adobe Bridge,进入Bridge界面后,再点帮助——更新即可。
方法2、下载后安装:进入Adobe官方网站,下载Camera Raw 8.2 Update,解压后双击AdobePatchInstaller.exe,即可完成更新。
方法3、Camera Raw 8BI文件替换更新:
(1)32位Win7:先解压,打开解压后的文件夹——再打开payloads\AdobeCameraRaw8.0All-文件夹,解压Assets1_1.zip,得到一个“1003”文件,将其重新命名为Camera Raw.8bi,这个就是32位的Camera Raw 8BI文件。将Camera Raw.8bi其拷贝到C:\\Program Files(x86)\Common Files\Adobe\Plug-Ins\CC\File Formats文件夹内,替换原来的同名文件即可。
(2)64位Win7:打开解压后的文件夹——再打开payloads\AdobeCameraRaw8.0All-x64-文件夹,解压Assets1_1.zip,得到一个“1002”文件,将其重新命名为Camera Raw.8bi,这个就是64位的Camera Raw 8BI文件。C:\\Program Files\Common Files\Adobe\Plug-Ins\CC\File Formats文件夹内,替换原来的同名文件即可。
红圈、金圈都是好圈。
威望1600 金钱2925 帖子精华0好友注册时间最后登录
白银会员, 积分 1600, 距离下一级还需 400 积分
当前用户组为 白银会员当前积分为 1600, 升到下一级还需要 400 点。
山东红圈老师,照你第三种方法做了,成功晋级!你的热忱象一股热流温暖全身!
威望15670 金钱25830 帖子精华0好友居住地山东省 滨州市注册时间最后登录
西峰旧雨 发表于
山东红圈老师,照你第三种方法做了,成功晋级!你的热忱象一股热流温暖全身!
&内存不足&的问题解决了吗?
红圈、金圈都是好圈。
威望1600 金钱2925 帖子精华0好友注册时间最后登录
白银会员, 积分 1600, 距离下一级还需 400 积分
当前用户组为 白银会员当前积分为 1600, 升到下一级还需要 400 点。
红圈老师,按您说的,改善了内存性能,速度快多了。关于裁剪,我改用“经典模式”就没有背景层自动解锁情况了。此贴我觉得对于使用高像素相机以RAW格式拍摄,用PS后期的朋友来说,确有普遍性。
威望35690 金钱39913 帖子精华0好友居住地北京市 海淀区注册时间最后登录
谢谢!我要试试Camera Raw8.2 的升级!
威望130 金钱298 帖子精华0好友居住地河南省 郑州市 管城回族区 紫荆山南路街道注册时间最后登录
中级会员, 积分 130, 距离下一级还需 70 积分
当前用户组为 中级会员当前积分为 130, 升到下一级还需要 70 点。
这个教程也很好,以前总是打开photoshop时必须经过一个漫长的等待,按照红圈老师的说解后,速度确实快了,但是必须要不厌其烦的清理缓存,呵呵!
威望40 金钱90 帖子精华0好友居住地四川省 成都市注册时间最后登录
新来乍到, 积分 40, 距离下一级还需 10 积分
当前用户组为 新来乍到当前积分为 40, 升到下一级还需要 10 点。
我也遇到同样问题,安装Win 7(64)后完美解决。
威望2890 金钱5637 帖子精华1好友居住地广东省 清远市注册时间最后登录
黄金会员, 积分 2890, 距离下一级还需 2110 积分
当前用户组为 黄金会员当前积分为 2890, 升到下一级还需要 2110 点。
受益匪浅,谢谢楼主老师。
威望10 金钱30 帖子精华0好友居住地四川省 遂宁市 蓬溪县 蓬南镇注册时间最后登录
新来乍到, 积分 10, 距离下一级还需 40 积分
当前用户组为 新来乍到当前积分为 10, 升到下一级还需要 40 点。
集成显卡的电脑会出现这个问题,它说的内存不足,指的是显卡的内存,设置使用独立显卡就问题解 决了,我没有设置好的时候,8G的内存都不行,主要是光照效果的时候
威望350 金钱910 帖子精华0好友居住地辽宁省 鞍山市 岫岩满族自治县 岫岩镇注册时间最后登录
高级会员, 积分 350, 距离下一级还需 150 积分
当前用户组为 高级会员当前积分为 350, 升到下一级还需要 150 点。
觉得要经常刷新桌面,每处理完一批图片就要退出PS,刷新桌面后,再进PS,这也是一个不错的选择。
威望101000 金钱30141 帖子精华3好友注册时间最后登录
主要还是D版的原因吧。
威望2710 金钱7497 帖子精华0好友居住地上海市 闵行区 古美街道注册时间最后登录
黄金会员, 积分 2710, 距离下一级还需 2290 积分
当前用户组为 黄金会员当前积分为 2710, 升到下一级还需要 2290 点。
关键是要安装64位系统
使用QQ帐号登录论坛的用户
发帖量超过200
发表新主题超过200
发帖量超过500
威望值超过8000
威望值超过10000
注册访问本站达5年之久的元老会员纪念
注册满百天,发言满百帖纪念
博客篇数超过100&&[&& 原创&&]&& 作者:&&|&&责编:王晔
问题:主板发出警报&主板无法点亮问题源:内存问题&&&&内存条原因出现此类故障一般是因为内存条与主板内存插槽接触不良造成。(开机时会有蜂鸣警报)只要用橡皮擦来回擦试其金手指部位即可解决问题(不要用酒精等清洗),还有就是内存损坏或主板内存槽有问题也会造成此类故障。内存条安装位置检修一:内存条金手指&&&&内存问题首先检查下内存有没有插紧;一般在搬动过后有可能内存变松动造成开机故障。再者可能是主机积灰过多造成内存短路,可以先吹一吹内存插槽附近的灰,待清理干净后再用橡皮擦拭内存条的金手指。&&&&同时笔者还建议内存条安装最好使用同一型号的,并且按照插槽颜色选择双通道安装。可避免在使用中出现得一些频率一集读取速率等方面得问题。
提示:支持键盘“← →”键翻页
机箱类型 机箱结构
天津重庆哈尔滨沈阳长春石家庄呼和浩特西安太原兰州乌鲁木齐成都昆明贵阳长沙武汉郑州济南青岛烟台合肥南京杭州东莞南宁南昌福州厦门深圳温州佛山宁波泉州惠州银川
本城市下暂无经销商
硬件论坛精选
下载中关村在线Android 客户端
下载中关村在线 iPhone 客户端
下载中关村在线Windows8客户端
成为中关村在线微信好友
4¥5995¥2796¥1997¥2298¥2399¥59910¥229C语言内存使用的常见问题及解决之道
& & 本文所讨论的&内存&主要指(静态)数据区、堆区和栈区空间(详细的布局和描述参考《Linux虚拟地址空间布局》一文)。数据区内存在程序编译时分配,该内存的生存期为程序的整个运行期间,如全局变量和static关键字所声明的静态变量。函数执行时在栈上开辟局部自动变量的储存空间,执行结束时自动释放栈区内存。堆区内存亦称动态内存,由程序在运行时调用malloc/calloc/realloc等库函数申请,并由使用者显式地调用free库函数释放。堆内存比栈内存分配容量更大,生存期由使用者决定,故非常灵活。然而,堆内存使用时很容易出现内存泄露、内存越界和重复释放等严重问题。
二 &内存问题
2.1 数据区内存
2.1.1 内存越界
& & &内存越界访问分为读越界和写越界。读越界表示读取不属于自己的数据,如读取的字节数多于分配给目标变量的字节数。若所读的内存地址无效,则程序立即崩溃;若所读的内存地址有效,则可读到随机的数据,导致不可预料的后果。写越界亦称&缓冲区溢出&,所写入的数据对目标地址而言也是随机的,因此同样导致不可预料的后果。
& & &内存越界访问会严重影响程序的稳定性,其危险在于后果和症状的随机性。这种随机性使得故障现象和本源看似无关,给排障带来极大的困难。
& & &数据区内存越界主要指读写某一数据区内存(如全局或静态变量、数组或结构体等)时,超出该内存区域的合法范围。
& & &写越界的主要原因有两种:1) memset/memcpy/memmove等内存覆写调用;2) 数组下标超出范围。
&1 #define NAME_SIZE &5
&2 #define NAME_LEN & NAME_SIZE-1/*Terminator*/
&3 char gszName[NAME_SIZE] = &Mike&;
&4 char *pszName = &Jason&;
&5 int main(void)
&7 & & memset(gszName, 0, NAME_SIZE+1); //越界1
&8 & & gszName[NAME_SIZE] = 0; & & & & &//越界2
10 & & if(strlen(pszName) &= NAME_SIZE) &//越界3(注意'='号)
11 & & & & strcpy(gszName, pszName);
13 & & int dwSrcLen = strlen(pszName);
14 & & if(dwSrcLen & NAME_SIZE)
15 & & & & memcpy(gszName, pszName, dwSrcLen); //未拷贝结束符('\0')
17 & & return 0;
& & &使用数组时,经常发生下标&多1&或&少1&的操作,特别是当下标用于for循环条件表达式时。此外,当数组下标由函数参数传入或经过复杂运算时,更易发生越界。
&1 void ModifyNameChar(unsigned char ucCharIdx, char cModChar)
&3 & & gszName[ucCharIdx] = cModC &//写越界
&5 int main(void)
&7 & & ModifyNameChar(5, 'L');
&8 & & unsigned char ucIdx = 0;
&9 & & for(; ucIdx &= NAME_SIZE; ucIdx++) &//'='号导致读越界
10 & & & & printf(&NameChar = %c\n&, gszName[ucIdx]);
12 & & return 0;
& & &对于重要的全局数据,可将其植入结构体内并添加CHK_HEAD和CHK_TAIL进行越界保护和检查:
&1 #define CODE_SIZE & & & 4 &//越界保护码的字节数
&2 #if (1 == CODE_SIZE)
&3 & & #define CODE_TYPE & char
&4 & & #define CHK_CODE & &0xCC & & & //除0外的特殊值
&5 #elif (2 == CODE_SIZE)
&6 & & #define CODE_TYPE & short
&7 & & #define CHK_CODE & &0xCDDC & & //除0外的特殊值
&9 & & #define CODE_TYPE & int
10 & & #define CHK_CODE & &0xABCDDCBA //除0外的特殊值
12 #define CHK_HEAD & &CODE_TYPE ChkH
13 #define CHK_TAIL & &CODE_TYPE ChkT
14 #define INIT_CHECK(ptChkMem) do{ \
15 & & (ptChkMem)-&ChkHead = CHK_CODE; \
16 & & (ptChkMem)-&ChkTail = CHK_CODE; \
17 }while(0)
18 #define CHK_OVERRUN(ptChkMem) do{ \
19 & & if((ptChkMem)-&ChkHead != CHK_CODE || (ptChkMem)-&ChkTail != CHK_CODE) { \
20 & & & & printf(&[%s(%d)&%s&]Memory Overrun(ChkHead:0x%X,ChkTail:0x%X)!\n&, __FILE__, __LINE__, FUNC_NAME, \
21 & & & & (ptChkMem)-&ChkHead, (ptChkMem)-&ChkTail); \
22 & & } \
23 }while(0)
24 typedef struct{
25 & & CHK_HEAD; &
26 & & char szName[NAME_SIZE];
27 & & CHK_TAIL; &
28 }T_CHK_MEM;
29 T_CHK_MEM gtChkM
30 int main(void)
32 & & memset(&gtChkMem, 0, sizeof(T_CHK_MEM));
33 & & INIT_CHECK(&gtChkMem);
35 & & memset(&gtChkMem, 11, 6);
36 & & CHK_OVERRUN(&gtChkMem);
37 & & strcpy(gtChkMem.szName, &Elizabeth&);
38 & & CHK_OVERRUN(&gtChkMem);
40 & & return 0;
& & &执行结果如下,可见被检查的szName数组其头尾地址均发生越界:
1 [test.c(177)&main&]Memory Overrun(dwChkHead:0xB0B0B0B,dwChkTail:0xABCDDCBA)!
2 [test.c(179)&main&]Memory Overrun(dwChkHead:0xB0B0B0B,dwChkTail:0xABCD0068)!
& & &若模块提供有全局数据的访问函数,则可将越界检查置于访问函数内:
&1 #ifdef CHK_GLOBAL_OVERRUN
&2 & & #define CODE_SIZE & & & 4 &//越界保护码的字节数
&3 & & #if (1 == CODE_SIZE)
&4 & & & & #define CODE_TYPE & char
&5 & & & & #define CHK_CODE & &(CODE_TYPE)0xCC & & & //除0外的特殊值
&6 & & #elif (2 == CODE_SIZE)
&7 & & & & #define CODE_TYPE & short
&8 & & & & #define CHK_CODE & &(CODE_TYPE)0xCDDC & & //除0外的特殊值
&9 & & #else
10 & & & & #define CODE_TYPE & int
11 & & & & #define CHK_CODE & &(CODE_TYPE)0xABCDDCBA //除0外的特殊值
12 & & #endif
13 & & #define CHK_HEAD & & & &CODE_TYPE ChkHead
14 & & #define CHK_TAIL & & & &CODE_TYPE ChkTail
15 & & #define HEAD_VAL(pvGlblAddr) & & & & & & (*(CODE_TYPE*)(pvGlblAddr))
16 & & #define TAIL_VAL(pvGlblAddr, dwGlbSize) &(*(CODE_TYPE*)((char*)pvGlblAddr+dwGlbSize-sizeof(CODE_TYPE)))
18 & & #define INIT_CHECK(pvGlblAddr, dwGlbSize) do{\
19 & & & & HEAD_VAL(pvGlblAddr) = TAIL_VAL(pvGlblAddr, dwGlbSize) = CHK_CODE;}while(0)
20 & & #define CHK_OVERRUN(pvGlblAddr, dwGlbSize, pFileName, dwCodeLine) do{\
21 & & & & if((HEAD_VAL(pvGlblAddr) != CHK_CODE) || (TAIL_VAL(pvGlblAddr, dwGlbSize) != CHK_CODE)) {\
22 & & & & & & printf(&[%s(%d)]Memory Overrun(ChkHead:0x%X,ChkTail:0x%X)!\n&, pFileName, dwCodeLine, \
23 & & & & & & HEAD_VAL(pvGlblAddr), TAIL_VAL(pvGlblAddr, dwGlbSize)); \
24 & & & & }}while(0)
26 & & #define INIT_GLOBAL(pvGlblAddr, dwInitVal, dwGlbSize) \
27 & & & & & & InitGlobal(pvGlblAddr, dwInitVal, dwGlbSize, __FILE__, __LINE__)
28 & & #define SET_GLOBAL(pvGlblAddr, pvGlblVal, dwGlbSize) \
29 & & & & & & SetGlobal(pvGlblAddr, pvGlblVal, dwGlbSize, __FILE__, __LINE__)
30 & & #define GET_GLOBAL(pvGlblAddr, pvGlblVal, dwGlbSize) \
31 & & & & & & GetGlobal(pvGlblAddr, pvGlblVal, dwGlbSize, __FILE__, __LINE__)
33 & & #define CHK_CODE & &0
34 & & #define CHK_HEAD
35 & & #define CHK_TAIL
36 & & #define HEAD_VAL(pvGlblAddr) & & & & & & & 0
37 & & #define TAIL_VAL(pvGlblAddr, dwGlbSize) & &0
38 & & #define INIT_CHECK(pvGlblAddr, dwGlbSize)
39 & & #define CHK_OVERRUN(pvGlblAddr, dwGlbSize, pFileName, dwCodeLine)
41 & & #define INIT_GLOBAL(pvGlblAddr, dwInitVal, dwGlbSize) do{\
42 & & & & & & memset(pvGlblAddr, dwInitVal, dwGlbSize);}while(0)
43 & & #define SET_GLOBAL(pvGlblAddr, pvGlblVal, dwGlbSize) do{\
44 & & & & & & memcpy(pvGlblAddr, pvGlblVal, dwGlbSize);}while(0)
45 & & #define GET_GLOBAL(pvGlblAddr, pvGlblVal, dwGlbSize) do{\
46 & & & & & & memcpy(pvGlblVal, pvGlblAddr, dwGlbSize);}while(0)
49 void InitGlobal(void* pvGlblAddr, int dwInitVal, unsigned int dwGlbSize,
50 & & & & & & & & const char* pFileName, INT32U dwCodeLine)
52 & & if(NULL == pvGlblAddr)
54 & & & & printf(&[%s(%d)]Null Pointer!\n&, pFileName, dwCodeLine);
55 & & & &
58 & & memset(pvGlblAddr, dwInitVal, dwGlbSize);
59 & & INIT_CHECK(pvGlblAddr, dwGlbSize);
61 void SetGlobal(void* pvGlblAddr, void* pvGlblVal, unsigned int dwGlbSize,
62 & & & & & & & &const char* pFileName, INT32U dwCodeLine)
64 & & if((NULL == pvGlblAddr) || (NULL == pvGlblVal))
66 & & & & printf(&[%s(%d)]Null Pointer: (%p), (%p)!\n&, pFileName, dwCodeLine, pvGlblAddr, pvGlblVal);
67 & & & &
70 & & memcpy(pvGlblAddr, pvGlblVal, dwGlbSize);
71 & & CHK_OVERRUN(pvGlblAddr, dwGlbSize, pFileName, dwCodeLine);
73 void GetGlobal(void* pvGlblAddr, void* pvGlblVal, unsigned int dwGlbSize,
74 & & & & & & & &const char* pFileName, INT32U dwCodeLine)
76 & & if((NULL == pvGlblAddr) || (NULL == pvGlblVal))
78 & & & & printf(&[%s(%d)]Null Pointer: (%p), (%p)!\n&, pFileName, dwCodeLine, pvGlblAddr, pvGlblVal);
79 & & & &
82 & & memcpy(pvGlblVal, pvGlblAddr, dwGlbSize);
83 & & CHK_OVERRUN(pvGlblAddr, dwGlbSize, pFileName, dwCodeLine);
86 int main(void)
88 & & INIT_GLOBAL(&gtChkMem, 0, sizeof(T_CHK_MEM));
89 & & printf(&[%d]ChkHead:0x%X,ChkTail:0x%X!\n&, __LINE__, HEAD_VAL(&gtChkMem), TAIL_VAL(&gtChkMem, sizeof(T_CHK_MEM))); & &
90 & & T_CHK_MEM tChkM
91 & & GET_GLOBAL(&gtChkMem, &tChkMem, sizeof(T_CHK_MEM));
93 & & strcpy(tChkMem.szName, &Elizabeth&);
94 & & SET_GLOBAL(&gtChkMem, &tChkMem, sizeof(T_CHK_MEM));
96 & & return 0;
& & &其中,TAIL_VAL宏假定系统为1字节对齐(否则请置CODE_SIZE为4字节)。因0xCC默认为四字节(对应于0xFFFFFFCC),故需用(CODE_TYPE)0xCC做类型转换,否则CHK_OVERRUN宏内if判断恒为真。
& & &该检查机制的缺点是仅用于检测写越界,且拷贝和解引用次数增多,访问效率有所降低。读越界后果通常并不严重,除非试图读取不可访问的区域,否则难以也不必检测。
& & &数据区内存越界通常会导致相邻的全局变量被意外改写。因此若已确定被越界改写的全局变量,则可通过工具查看符号表,根据地址顺序找到前面(通常向高地址越界)相邻的全局数据,然后在代码中排查访问该数据的地方,看看有哪些位置可能存在越界操作。
& & &有时,全局数据被意外改写并非内存越界导致,而是某指针(通常为野指针)意外地指向该数据地址,导致其内容被改写。野指针导致的内存改写往往后果严重且难以定位。此时,可编码检测全局数据发生变化的时机。若能结合堆栈回溯(Call Backtrace),则通常能很快地定位问题所在。
& & &修改只读数据区内容会引发段错误(Segmentation Fault),但这种低级失误并不常见。一种比较隐秘的缺陷是函数内试图修改由指针参数传入的只读字符串,详见《关于Linux系统basename函数缺陷的思考》一文。
& & &因其作用域限制,静态局部变量的内存越界相比全局变量越界更易发现和排查。
& & 【对策】某些工具可帮助检查内存越界的问题,但并非万能。内存越界通常依赖于测试环境和测试数据,甚至在极端情况下才会出现,除非精心设计测试数据,否则工具也无能为力。此外,工具本身也有限制,甚至在某些大型项目中,工具变得完全不可用。
& & &与使用工具类似的是自行添加越界检测代码,如本节上文所示。但为求安全性而封装检测机制的做法在某种意义上得不偿失,既不及Java等高级语言的优雅,又损失了C语言的简洁和高效。因此,根本的解决之道还是在于设计和编码的审慎周密。相比事后检测,更应注重事前预防。
& & &编程时应重点走查代码中所有操作全局数据的地方,杜绝可能导致越界的操作,尤其注意内存覆写和拷贝函数memset/memcpy/memmove和数组下标访问。
& & &在内存拷贝时,必须确保目的空间大于或等于源空间。也可封装库函数使之具备安全校验功能,如:
&1 /******************************************************************************
&2 * 函数名称: &StrCopy
&3 * 功能说明: &带长度安全拷贝字符串
&4 * 输入参数: &dwSrcLen : 目的字符串缓冲区长度
&5 & & & & & & pSrcStr &: 源字符串
&6 & & & & & & dwSrcLen : 源字符串长度(含终止符'\0')
&7 * 输出参数: &pDstStr &: 目的字符串缓冲区
&8 * 返回值 &: &成功: ptD 失败: &Nil&
&9 * 用法示例: &char *pSrcStr = &HelloWorld&; char szDstStr[20] = {0};
10 & & & & & & StrCopy(szDstStr, sizeof(szDstStr), pSrcStr, strlen(pSrcStr))+1);
11 * 注意事项: &拷贝长度为min(dwDstLen, dwSrcLen) - 1{Terminator}
12 ******************************************************************************/
13 char *StrCopy(char *pDstStr, int dwDstLen, char *pSrcStr, int dwSrcLen)
15 & & if(((NULL == pDstStr) || (NULL == pSrcStr)) ||
16 & & & &((0 == dwDstLen) || (0 == dwSrcLen)))
17 & & & & return (char *)&Nil&;
19 & & int dwActLen = (dwDstLen &= dwSrcLen) ? dwDstLen : dwSrcL
20 & & pDstStr[dwActLen - 1] = '\0';
22 & & return strncpy(pDstStr, pSrcStr, dwActLen - 1);
& & &在使用memcpy和strcpy拷贝字符串时应注意是否包括结束符(memcpy不自动拷贝&\0&)。
& & &按照下标访问数组元素前,可进行下标合法性校验:
1 /* 数组下标合法性校验宏 */
2 #define CHECK_ARRAY_INDEX(index, maxIndex) do{\
3 & & if(index & maxIndex) { \
4 & & &printf(&Too large &#index&: %d(Max: %d)!!!\n\r&, index, maxIndex); \
5 & & &index = maxI \
7 }while(0)
2.1.2 多重定义
& & &函数和定义时已初始化的全局变量是强符号;未初始化的全局变量是弱符号。多重定义的符号只允许最多一个强符号。Unix链接器使用以下规则来处理多重定义的符号:
& & &规则一:不允许有多个强符号。在被多个源文件包含的头文件内定义的全局变量会被定义多次(预处理阶段会将头文件内容展开在源文件中),若在定义时显式地赋值(初始化),则会违反此规则。
& & &规则二:若存在一个强符号和多个弱符号,则选择强符号。
& & &规则三:若存在多个弱符号,则从这些弱符号中任选一个。
& & &当不同文件内定义同名(即便类型和含义不同)的全局变量时,该变量共享同一块内存(地址相同)。若变量定义时均初始化,则会产生重定义(multiple definition)的链接错误;若某处变量定义时未初始化,则无链接错误,仅在因类型不同而大小不同时可能产生符号大小变化(size of symbol `XXX' changed)的编译警告。在最坏情况下,编译链接正常,但不同文件对同名全局变量读写时相互影响,引发非常诡异的问题。这种风险在使用无法接触的第三方库时尤为突出。
& & &下面的例子编译链接时没有任何警告和错误,但结果并非所愿:
&1 //test.c
&2 int gdwCount = 0;
&3 int GetCount(void)
&5 & & return gdwC
&9 //main.c
10 extern int GetCount(void);
11 int gdwC
12 int main(void)
14 & & gdwCount = 10;
15 & & printf(&GetCount=%d\n&, GetCount());
16 return 0;
& & &编码者期望函数GetCount的返回值打印出来是0,但其实是10。若将main.c中的int gdwCount语句改为int gdwCount = 0,编译链接时就会报告multiple definition of 'gdwCount'的错误。因此尽量不要依赖和假设这种符号规则。
& & &关于全局符号多重定义的讨论,详见《C语言头文件组织与包含原则》一文。
& & 【对策】尽量避免使用全局变量。若确有必要,应采用静态全局变量(无强弱之分,且不会和其他全局符号产生冲突),并封装访问函数供外部文件调用。
2.1.3 volatile修饰
& & &关键字volatile用于修饰易变的变量,告诉编译器该变量值可能会在任意时刻被意外地改变,因此不要试图对其进行任何优化。每次访问(读写)volatile所修饰的变量时,都必须从该变量的内存区域中重新读取,而不要使用寄存器(CPU)中保存的值。这样可保证数据的一致性,防止由于变量优化而出错。
& & &以下几种情况通常需要volatile关键字:
外围并行设备的硬件寄存器(如状态寄存器);
中断服务程序(ISR)中所访问的非自动变量(Non-automatic Variable),即全局变量;
多线程并发环境中被多个线程所共享的全局变量。
& & &变量可同时由const和volatile修饰(如只读的状态寄存器),表明它可能被意想不到地改变,但程序不应试图修改它。指针可由volatile修饰(尽管并不常见),如中断服务子程序修改一个指向某buffer的指针时。又如:
1 //只读端口(I/O与内存共享地址空间,非IA架构)
2 const volatile char *port = (const volatile char *)0x01F7
& & &误用volatile关键字可能带来意想不到的错误,例如:
1 int CalcSquare(volatile int *pVal)
3 & & return (*pVal) * (*pVal);
4 } //deficient
& & &函数CalcSquare返回指针pVal所指向值的平方,但由于该值被volatile修饰,编译器将产生类似下面的代码:
1 int CalcSquare(volatile int *pVal)
3 & & int dwTemp1, dwTemp2;
4 & & dwTemp1 = *pV
5 & & dwTemp2 = *pV
6 & & return dwTemp1 * dwTemp2;
7 }//deficient
& & &多线程环境下,指针pVal所指向值在函数CalcSquare执行时可能被意想不到地该变,因此dwTemp1和dwTemp2的取值可能不同,最终未必返回期望的平方值。
& & &正确的代码如下(使用全局变量的拷贝也是提高线程安全性的一种方法):
1 long CalcSquare(volatile int *pVal)
3 & & int dwT
4 & & dwTemp = *pV
5 & & return dwTemp * dwT
6 }//deficient
& & &再举一例:
1 #define READ(val, addr) &(val = *(unsigned long *)addr)
& & &编译器优化这段代码时,若addr地址的数据读取太频繁,优化器会将该地址上的值存入寄存器中,后续对该地址的访问就转变为直接从寄存器中读取数据,如此将大大加快数据读取速度。但在并发操作时,一个进程读取数据,另一进程修改数据,这种优化就会造成数据不一致。此时,必须使用volatile修饰符。
& & 【对策】合理使用volatile修饰符。
2.2 栈区内存
2.2.1 内存未初始化
& & &未初始化的栈区变量其内容为随机值。直接使用这些变量会导致不可预料的后果,且难以排查。
& & &指针未初始化(野指针)或未有效初始化(如空指针)时非常危险,尤以野指针为甚。
& & 【对策】在定义变量时就对其进行初始化。某些编译器会对未初始化发出警告信息,便于定位和修改。
2.2.2 堆栈溢出
& & &每个线程堆栈空间有限,稍不注意就会引起堆栈溢出错误。注意,此处&堆栈&实指栈区。
1 #define MAX_SIZE &3200000 &//不同该值不同(ulimit &s: 10240kbytes)
2 int main(void){
3 & & int aStackCrasher[MAX_SIZE] = {0}; &//可能导致Segmentation fault
4 & & aStackCrasher[0] = 1;
5 & & return 0;
& & &堆栈溢出主要有两大原因:1) 过大的自动变量;2) 递归或嵌套调用层数过深。
& & &有时,函数自身并未定义过大的自动变量,但其调用的系统库函数或第三方接口内使用了较大的堆栈空间(如printf调用就要使用2k字节的栈空间)。此时也会导致堆栈溢出,并且不易排查。
& & &此外,直接使用接口模块定义的数据结构或表征数据长度的宏时也存在堆栈溢出的风险,如:
1 typedef struct{
2 & & unsigned short wV
3 & & unsigned char aMacAddr[6];
4 & & unsigned char ucMacT
5 }T_MAC_ADDR_ENTRY;
6 typedef struct{
7 & & unsigned int dwTotalAddrN
8 & & T_MAC_ADDR_ENTRY tMacAddrEntry[MAX_MACTABLE_SIZE];
9 }T_MAC_ADDR_TABLE;
& & &上层模块在自行定义的T_MAC_ADDR_TABLE结构中,使用底层接口定义的MAX_MACTABLE_SIZE宏指定MAC地址表最大条目数。接口内可能会将该宏定义为较大的值(如8000个条目),上层若直接在栈区使用TABLE结构则可能引发堆栈溢出。
& & &在多线程环境下,所有线程栈共享同一虚拟地址空间。若应用程序创建过多线程,可能导致线程栈的累计大小超过可用的虚拟地址空间。在用pthread_create反复创建一个线程(每次正常退出)时,可能最终因内存不足而创建失败。此时,可在主线程创建新线程时指定其属性为PTHREAD_CREATE_DETACHED,或创建后调用pthread_join,或在新线程内调用pthread_detach,以便新线程函数返回退出或pthread_exit时释放线程所占用的堆栈资源和线程描述符。
& & 【对策】应该清楚所用平台的资源限制,充分考虑函数自身及其调用所占用的栈空间。对于过大的自动变量,可用全局变量、静态变量或堆内存代替。此外,嵌套调用最好不要超过三层。
2.2.3 内存越界
& & &因其作用域和生存期限制,发生在栈区的内存越界相比数据区更易发现和排查。
& & &下面的例子存在内存越界,并可能导致段错误:
1 int bIsUniCommBlv = 1;
2 int main(void)
4 & & char szWanName[] = &OAM_WAN_VOIP&;
5 & & if(bIsUniCommBlv)
6 & & & & strcpy(szWanName, &OAM_WAN_MNGIP&);
8 & & return 0;
& & &但该例的另一写法则更为糟糕:
&1 int bIsUniCommBlv = 1;
&2 int main(void)
&4 & & char szWanName[] = &&; //字符数组szWanName仅能容纳1个元素('\0')!
&5 & & if(bIsUniCommBlv)
&6 & & & & strcpy(szWanName, &OAM_WAN_MNGIP&);
&7 & & else
&8 & & & & strcpy(szWanName, & OAM_WAN_VOIP&);
10 & & return 0;
& & &函数传递指针参数时也可能发生内存越界:
&1 typedef struct{
&2 & & int dwErrNo;
&3 & & int aErrInfo[6];
&4 }T_ERR_INFO;
&5 int PortDftDot1p(int dwPort, int dwDot1p, void *pvOut)
&7 & & int dwRet = 0;
&8 & & T_ERR_INFO *ptErrInfo = (T_ERR_INFO *)pvO
&9 & & //dwRet = DoSomething();
10 & & ptErrInfo-&dwErrNo & & = dwR
11 & & ptErrInfo-&aErrInfo[0] = dwP
12 & & return dwR
15 int main(void)
17 & & int dwOut = 0;
18 & & PortDftDot1p(0, 5, &dwOut);
19 & & return 0;
& & &上例中,接口函数PortDftDot1p使用T_ERR_INFO结构向调用者传递出错信息,但该结构并非调用者必知和必需。出于隐藏细节或其他原因,接口将出参指针声明为void*类型,而非T_ERR_INFO*类型。这样,当调用者传递的相关参数为其他类型时,编译器也无法发现类型不匹配的错误。此外,接口内未对pvOut指针判空就进行类型转换,非常危险(即使判空依旧危险)。从安全和实用角度考虑,该接口应该允许pvOut指针为空,此时不向调用者传递出错信息(调用方也许并不想要这些信息);同时要求传入pvOut指针所指缓冲区的字节数,以便在指针非空时安全地传递出错信息。
& & &错误的指针偏移运算也常导致内存越界。例如,指针p+n等于(char*)p + n * sizeof(*p),而非(char*)p + n。若后者才是本意,则p+n的写法很可能导致内存越界。
& & &栈区内存越界还可能导致函数返回地址被改写,详见《缓冲区溢出详解》一文。
& & &两种情况可能改写函数返回地址:1) 对自动变量的写操作超出其范围(上溢);2) 主调函数和被调函数的参数不匹配或调用约定不一致。
& & &函数返回地址被改写为有效地址时,通过堆栈回溯可看到函数调用关系不符合预期。当返回地址被改写为非法地址(如0)时,会发生段错误,并且堆栈无法回溯:
1 Program received signal SIGSEGV, Segmentation fault.
2 0x in ?? ()
& & &这种故障从代码上看特征非常明显,即发生在被调函数即将返回的位置。
& & 【对策】与数据区内存越界对策相似,但更注重代码走查而非越界检测。
2.2.4 返回栈内存地址
& & &(被调)函数内的局部变量在函数返回时被释放,不应被外部引用。虽然并非真正的释放,通过内存地址仍可能访问该栈区变量,但其安全性不被保证。详见《已释放的栈内存》一文。
&1 const static char *paMsgNameMap[] = {
&2 & & /* 0 */ & & &0&,
&3 & & /* 1 */ & & &1&,
&4 & & /* 2 */ & & &2&,
&5 & & /* 3 */ & & &3&,
&6 & & /* 4 */ & & &Create&,
&7 & & /* 5 */ & & &5&,
&8 & & /* 6 */ & & &Delete&,
&9 & & /* 7 */ & & &7&,
10 & & /* 8 */ & & &Set&,
11 & & /* 9 */ & & &Get&,
12 & & //... ...
13 & & /*28 */ & & &GetCurData&,
14 & & /*29 */ & & &SetTable&
16 const static unsigned char ucMsgNameNum = sizeof(paMsgNameMap) / sizeof(paMsgNameMap[0]);
18 char *ParseOmciMsgType(unsigned char ucMsgType)
20 & & if(ucMsgType & ucMsgNameNum)
21 & & & & return paMsgNameMap[ucMsgType];
23 & & char szStrMsgType[sizeof(&255&)] = {0}; &/* Max:&255& */
24 & & sprintf(szStrMsgType, &%u&, ucMsgType);
25 & & return szStrMsgT &//编译警告:
& & &编译上述代码,函数ParseOmciMsgType在返回szStrMsgType处产生function returns address of local variable的警告。可将szStrMsgType定义为静态变量:
1 char *ParseOmciMsgType(unsigned char ucMsgType)
3 & & if(ucMsgType & ucMsgNameNum)
4 & & & & return paMsgNameMap[ucMsgType];
6 & & static char szStrMsgType[sizeof(&255&)] = {0}; /* Max:&255& */
7 & & sprintf(szStrMsgType, &%u&, ucMsgType);
8 & & return szStrMsgT
& & &若将结果通过函数参数而非返回值传递,则代码会更为安全:
1 void ParseOmciMsgType(unsigned char ucMsgType, char *pszMsgType)
3 & & if(ucMsgType & ucMsgNameNum)
4 & & & & strcpy(pszMsgType, paMsgNameMap[ucMsgType]);
5 & & else
6 & & & & sprintf(pszMsgType, &%u&, ucMsgType);
& & &注意,不可采用下面的写法:
1 void ParseOmciMsgType(unsigned char ucMsgType, char *pszMsgType)
3 & & if(ucMsgType & ucMsgNameNum)
4 & & & & pszMsgType = paMsgNameMap[ucMsgType];
5 & & else
6 & & & & sprintf(pszMsgType, &%u&, ucMsgType);
& & &因为指针做为函数参数时,函数内部只能改变指针所指向地址的内容,并不能改变指针的指向。
& & &若线程在自身栈上分配一个数据结构并将指向该结构的指针传递给pthread_exit,则调用pthread_join的线程试图使用该结构时,原先的栈区内存可能已被释放或另作他用。
& & 【对策】不要用return语句返回指向栈内变量的指针,可改为返回指向静态变量或动态内存的指针。但两者都存在重入性问题,而且后者还存在内存泄露的危险。
2.3 堆区内存
2.3.1 内存未初始化
& & &通过malloc库函数分配的动态内存,其初值未定义。若访问未初始化或未赋初值的内存,则会获得垃圾值。当基于这些垃圾值控制程序逻辑时,会产生不可预测的行为。
& & 【对策】在malloc之后调用 memset 将内存初值清零,或使用 calloc代替malloc。
1 char *pMem = malloc (10);
2 memset(pMem, 0, 10); // memset前应对申请的动态内存做有效性检查
4 char *pMem = calloc (10, 1);
2.3.2 内存分配失败
& & &动态内存成功分配的前提是系统具有足够大且连续可用的内存。内存分配失败的主要原因有:
& & &1) 剩余内存空间不足;
& & &2) 剩余内存空间充足,但内存碎片太多,导致申请大块内存时失败;
& & &3) 内存越界,导致malloc等分配函数所维护的管理信息被破坏。
& & &剩余内存空间不足的情况相对少见,通常发生在申请超大块内存时。例如:
&1 #include &stdlib.h&
&2 #include &errno.h&
&3 #define ALLOC_BYTES & (24)
&4 int main(void){
&5 & & unsigned int dwRound = 0;
&6 & & while(1){
&7 & & & & char *pMem = malloc(ALLOC_BYTES);
&8 & & & & if(NULL == pMem){
&9 & & & & & & printf(&Alloc failed(%s)!\n&, strerror(errno));
10 & & & & & & return -1;&
11 & & & & }&
12 & & & & printf(&%d -& 0x%p\n&, dwRound, pMem);&
13 & & & & dwRound++;
15 & & return 0;
& & &执行后产生内存分配失败的错误:
1 0 -& 0x77f6b008
2 1 -& 0x37f6a008
3 Alloc failed(Cannot allocate memory)!
& & &内存越界导致内存分配失败的情况更为常见。此时,可从分配失败的地方开始回溯最近那个分配成功的malloc,看附近是否存在内存拷贝和数组越界的操作。
& & 【对策】若申请的内存单位为吉字节(GigaByte),可考虑选用64位寻址空间的机器,或将数据暂存于硬盘文件中。此外,申请动态内存后,必须判断内存是否是为NULL,并进行防错处理,比如使用return语句终止本函数或调用exit(1)终止整个程序的运行。
2.3.3 内存释放失败
& & &内存释放失败的主要原因有:
& & &1) 释放未指向动态内存的指针;
& & &2) 指向动态内存的指针在释放前被修改;
& & &3) 内存越界,导致malloc等分配函数所维护的管理信息被破坏;
& & &4) 内存重复释放(Double Free)。
& & &情况1属于低级错误,即指针并未执行malloc分配,却调用free释放该指针指向的内存。
1 int main(void)
3 & & int dwMem = 0; //具有迷惑性的变量名
4 & & int *pBuf = &dwM
5 & & free(pBuf);
7 & & return 0;
9 //执行后报错:*** glibc detected *** ./test: free(): invalid pointer: 0xbf84b35c ***
& & &情况2多发生在从申请内存到最后释放跨越多个模块历经大量处理逻辑时,指针初始值被修改掉。简单示例如下:
&1 int main(void)
&3 & & char *pMem = malloc(10);
&4 & & if(NULL == pMem)
&5 & & & & return -1;&
&7 & & pMem++;
&8 & & free(pMem);
10 & & return 0;
12 //执行后报错:*** glibc detected *** ./test: free(): invalid pointer: 0x082b5009 ***
& & &内存越界也可能导致内存释放失败:
&1 int main(void)
&3 & & char *pMem = malloc(2);
&4 & & if(NULL == pMem)
&5 & & & & return -1;&
&7 & & memset(pMem, 0, sizeof(int)*10);
&8 & & free(pMem);
&9 & & return 0;
11 //执行后报错:*** glibc detected *** ./test: free(): invalid next size (fast): 0x09efa008 ***
& & &内存重复释放最简单但最不可能出现的示例如下:
&1 int main(void)
&3 & & char *pMem = malloc(10);
&4 & & if(NULL == pMem)
&5 & & & & return -1;&
&7 & & free(pMem);
&8 & & free(pMem);
10 & & return 0;
12 //执行后报错:*** glibc detected *** ./test: double free or corruption (fasttop): 0x ***
& & &通常,编码者会封装接口以更好地管理内存的申请和释放。若释放接口内部在释放前未判断指向动态内存的指针是否为空,或释放后未将指向该内存的指针设置为空。当程序中调用关系或处理逻辑过于复杂(尤其是对于全局性的动态内存),难以搞清内存何时或是否释放,加之接口未作必要的防护,极易出现内存重复释放。
& & &此外,当程序中存在多份动态内存指针的副本时,很容易经由原内存指针及其副本释放同一块内存。
&1 int main(void)
&3 & & char *pMem = malloc(sizeof(char)*10);
&4 & & if(NULL == pMem)
&5 & & & & return -1;
&7 & & char *pMemTemp = pM
&8 & & //Do Something...
10 & & free(pMem);
11 & & free(pMemTemp);
12 & & return 0;
& & &上例中仅需释放pMem或pMemTemp其一即可。
& & 【对策】幸运的是,内存释放失败会导致程序崩溃,故障明显。并且,可借助静态或动态的内存检测技术进行排查。
& & &对于重复释放,可仿照《C语言通用双向循环链表操作函数集》一文中介绍的SAFE_FREE宏,尽可能地&规避&其危害(但当内存指针存在多个副本时无能为力)。
1 #define SAFE_FREE(pointer) & SafeFree(&(pointer)) &//与SAFE_ALLOC入参指针形式一致
2 void SafeFree(void **pointer)
4 & & if(pointer != NULL)
6 & & & & free(*pointer);
7 & & & & *pointer = NULL;
& & &此外,应在设计阶段保证数据结构和流程尽量地简洁合理,从根本上解决对象管理的混乱。
2.3.4 内存分配与释放不配对
& & &编码者一般能保证malloc和free配对使用,但可能调用不同的实现。例如,同样是free接口,其调试版与发布版、单线程库与多线程库的实现均有所不同。一旦链接错误的库,则可能出现某个内存管理器中分配的内存,在另一个内存管理器中释放的问题。此外,模块封装的内存管理接口(如GetBuffer和FreeBuffer)在使用时也可能出现GetBuffer配free,或malloc配FreeBuffer的情况,尤其是跨函数的动态内存使用。
& & 【对策】动态内存的申请与释放接口调用方式和次数必须配对,防止内存泄漏。分配和释放最好由同一方管理,并提供专门的内存管理接口。
2.3.5 内存越界
& & &除明显的读写越界外,关于动态内存还存在一种sizeof计算错误导致的越界:
&1 int main(void)
&3 & & T_CHK_MEM *pMem = malloc(sizeof(pMem));
&4 & & if(NULL == pMem)
&5 & & & & return -1;
&7 & & memset(pMem, 0, sizeof(T_CHK_MEM));
&8 & & free(pMem);
&9 & & return 0;
11 //执行后报错:*** glibc detected *** ./test: free(): invalid next size (fast): 0x ***
& & &这种越界也是内存释放失败的一个原因。正确的内存申请写法应该是:
1 T_CHK_MEM *pMem = malloc(sizeof(*pMem));
3 T_CHK_MEM *pMem = malloc(sizeof(T_CHK_MEM));

我要回帖

更多关于 内存 的文章

 

随机推荐