C语言内存使用的常见问题及解决之道 -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

    一 前言

    本文所讨论的“内存”主要指(静态)数据区、堆区和栈区空间(详细的布局和描述参考《Linux虚拟地址空间布局》一文),

C语言内存使用的常见问题及解决之道

。数据区内存在程序编译时分配,该内存的生存期为程序的整个运行期间,如全局变量和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");

最新文章