设为首页 加入收藏

TOP

C语言内存使用的常见问题及解决之道(一)
2015-01-22 21:26:28 来源: 作者: 【 】 浏览:162
Tags:语言 内存 使用 常见问题 解决
一 ?前言
? ? 本文所讨论的“内存”主要指(静态)数据区、堆区和栈区空间(详细的布局和描述参考《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)
?6 {
?7 ? ? memset(gszName, 0, NAME_SIZE+1); //越界1
?8 ? ? gszName[NAME_SIZE] = 0; ? ? ? ? ?//越界2
?9 ? ? ??
10 ? ? if(strlen(pszName) <= NAME_SIZE) ?//越界3(注意'='号)
11 ? ? ? ? strcpy(gszName, pszName);
12 ??
13 ? ? int dwSrcLen = strlen(pszName);
14 ? ? if(dwSrcLen < NAME_SIZE)
15 ? ? ? ? memcpy(gszName, pszName, dwSrcLen); //未拷贝结束符('\0')
16?
17 ? ? return 0;
18 }
复制代码
? ? ?使用数组时,经常发生下标“多1”或“少1”的操作,特别是当下标用于for循环条件表达式时。此外,当数组下标由函数参数传入或经过复杂运算时,更易发生越界。
?
复制代码
?1 void ModifyNameChar(unsigned char ucCharIdx, char cModChar)
?2 {
?3 ? ? gszName[ucCharIdx] = cModChar; ?//写越界
?4 }
?5 int main(void)
?6 {
?7 ? ? ModifyNameChar(5, 'L');
?8 ? ? unsigned char ucIdx = 0;
?9 ? ? for(; ucIdx <= NAME_SIZE; ucIdx++) ?//'='号导致读越界
10 ? ? ? ? printf("NameChar = %c\n", gszName[ucIdx]);
11 ? ??
12 ? ? return 0;
13 }
复制代码
? ? ?对于重要的全局数据,可将其植入结构体内并添加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外的特殊值
?8 #else
?9 ? ? #define CODE_TYPE ? int
10 ? ? #define CHK_CODE ? ?0xABCDDCBA //除0外的特殊值
11 #endif
12 #define CHK_HEAD ? ?CODE_TYPE ChkHead;
13 #define CHK_TAIL ? ?CODE_TYPE ChkTail;
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 gtChkMem;
30 int main(void)
31 {
32 ? ? memset(>ChkMem, 0, sizeof(T_CHK_MEM));
33 ? ? INIT_CHECK(>ChkMem);
34 ? ??
35 ? ? memset(>ChkMem, 11, 6);
36 ? ? CHK_OVERRUN(>ChkMem);
37 ? ? strcpy(gtChkMem.szName, "Elizabeth");
38 ? ? CHK_OVERRUN(>ChkMem);
39?
40 ? ? return 0;
41 }
复制代码
? ? ?执行结果如下,可见被检查的szName数组其头尾地址均发生越界:
?
1 [test.c(177)
]Memory Overrun(dwChkHead:0xB0B0B0B,dwChkTail:0xABCDDCBA)!
2 [test.c(179)
]Memory Overrun(dwChkHead:0xB0B0B0B,dwChkTail:0xABCD0068)!
? ? ?若模块提供有全局数据的
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C语言里为何会有“2+2=5”的结果 下一篇排序(6)---------归并排序(C语言..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: