天天看点

C语言常见的内存错误总结

    作者:朱克锋

    对c语言的程序员来说,管理和使用内存可能是个困难的、容易出错的任务.与存储器有关的错误属于那些最令人惊恐的错误,因为他们经在运行的时间和空间上都在距错误根源很远的地方才表现出来,程序在最终失败之前已经运行了很长时间,而且程序终止的位置距离出错的位置已经很远了。所以这类错误很难被查出来,也很难发现。但是对于常见的内存错误如果很熟悉的话就会更早的发现与避免错误的发生。下面我总结了常见的与内存有关的错误:

  内存泄漏

   内存泄漏通常是缓慢的隐形的,当我们不小心忘记释放我们分配的内存就会发生这样的问题,看下面的这段代码:

void mem_leak(int n)

{

 int *gb_n = (int *)malloc(sizeof(int) * n);

 //gb_n 在这里没有被释放,也没有被引用

 return ;

}

   这段代码分配了一个内存快,在释放之前程序就返回了,如果这是一个服务器或守护进程的程序就很糟糕了,它会造成内存空间被占满的情况,一般现象是程序运行变慢最终终止。

   间接引用不良指针

    间接引用不良指针是一个常见的错误,其中最为经典的应该是scanf错误了,假设我们想要是用scanf从stdin读取一个整数到一个变量,正确的方法应该是传递给scanf一个格式串和变量的地址:

void scanf()

{

 scanf("%d",$val); 

}

   然而有些程序员特别是初学者,很容易出现以下错误:

void scanf_bug()

{

 scanf("%d",val);

}

   传递的是val的内容而不是他的地址,在这种情况下,scanf把变量的内容解释为一个地址,试图将一个字写到这个位置,最想的情况是程序立即终止,而在糟糕的情况下阿拉的内容被写到对应的内存中某个合法的读写区域,于是就会覆盖原来地址中的值,通常这样会造成灾难性的或令人困惑的后果.

    读未出世化的地址或存储器

    虽然.bss存储器的位置如未初始化的变量总是被初始化为零,但是对于堆来说就不是这样了,这种错误中最常见的是认为堆被初始化为零了:

int uninit_bug(int **array, int *p, int num)

{

 int i = 0;

 int j = 0;

 int *temp_p = (int *)malloc(sizeof(int) * num);

 for(i = 0; i < num; i++)

 {

  //temp_p[i] = NULL;

  for(j = 0; j  > num; j++)

  {

   temp_p[i] = array[i][j] * p[j];

  }

  return temp_p;

 }

}

   上面的这短代码中显示的是:

不正确地认为指针temp_p被初始化为零,正确的应该在程序中把

temp_p[i]设置为零:

temp_p[i] = NULL;

或者使用另一个方法calloc来动态分配内存:

int *temp_p = (int *)calloc(1, sizeof(int) * num);

方法calloc来动态分配内存时自动的将指针temp_p被初始化为零.

    错误的认为指针和它们指向的对象是相同大小的

    这种错误是错误的认为错误的认为指针和它们指向的对象是相同大小的:

int **poiter_to_obj_size(int num1,  int num2)

{

 int i = 0;

 int **p_array = (int **)malloc(sizeof(int) * num1);

 //

 *p_array = NULL;

 for(i = 0; i < num1; i++)

 {  

  p_array[i] = (int *)malloc(sizeof(int) * num2);

 }

 return p_array;

}

    现在我们来分析上面这段代码,这段代码的目的是创建一个由num1个指针组成的数组,每个指针都指向一个包含num2个int类型的数组.

    在程序中我们把:

    int **p_array = (int **)malloc(sizeof(int*) * num1);

    写成了:

    int **p_array = (int **)malloc(sizeof(int) * num1);

    这时程序实际上创建的是一个int类型的数组,这段代码会在int和指向int的指针大小相同的机器上运行良好,如果把这段代码放在int和指向int的指针大小不同的机器上运行就会出现一些令人困惑的奇怪的错误.

错位错误

     这种错误是一种很常见的覆盖错误看下面的这段代码:

int **off_by_one(int num 1, int num2)

{

 int i = 0;

 int **p_array = (int **)malloc(sizeof(int*) * num1);

 *p_array = NULL;

 //for(i = 0; i < num1; i++)

 for(i = 0; i <= num1; i++)

 {

  p_array[i] = (int *)malloc(sizeof(int) * num2);

 }

 return p_array;

}

    这段代码创建了一个num1个元素的指针数组,但是后面的代码却试图初始化数组的num1+1个元素,这样最后一个就会覆盖数组的后面的某个地址中的数据.

    错误地引用指针而不是它所指的对象

    错误地引用指针而不是它所指的对象,这种错误一般是由于C操作符的优先级和结合性引起的,这时我们会错误地操作指针而不是他所指向的对象先看一下下面的这个小的程序代码:

int *use_another_obj(int **binheap, int *size)

{

 int *pac = binheap[0];

 binheap[0] = binheap[*size - 1];

 *size--; 

 heapify(binheap, *size, 0);

 return pac;

}

    我本意是减少size指针指向的整数的值,却因为运算符的优先级出现了减少指针自己的值!正确的写法应该是这样的:

    binheap[0] = binheap[*size - 1];

    (*size)--;

    误解了指针运算

    首先看一下代码:

int *wrong_poiter_op(int *poiter, int num)

{

 while(*poiter && * != num)

 {

  poiter += sizeof(int);

 }

 return poiter;

}

    我想用这个程序遍历一个int类型的数组,返回一个指针指向num的首次出现,但是结果却不是我期望的那样,因为每次循环时poiter += sizeof(int);都把指针加上了4,这样代码就遍历了数组中的每4个整数了,正确的应该是这样的:

poiter++ += sizeof(int);

    允许栈缓冲区溢出

    假设一个程序不见查输入串的大小就写入栈的目标缓冲区,那么这个程序就会有缓冲区溢出的错误:

void bufoverflow_bug()

{

 char buf[256];

 gets(buf);

}

   上面的这段程序代码就出现了缓冲区错误,因为gets函数拷贝一个任意长度的串到缓冲区了,所以我们必须使用fgets函数:

fgets(buf);

因为这个函数限制了输入串的大小.

    引用已经释放掉的堆中的数据

    引用已经释放掉的堆中的数据,看下面的代码:

int *ref_freeed_heap_data(int n, int m)

{

 int i = 0;

        int *x = NULL;

 int *y = NULL;

 x = (int *)malloc(sizeof(int) * n);

 free(x); 

 y = (int *)malloc(sizeof(int) * m);

 for(i = 0; i < m; i++)

 {

  y[i] = x[i]++;

  //x freeed!!!!!!

 }

 return y;

}

    程序引用了已经释放的数据:free(x);

    引用不存在的变量

   下面的这个函数返回的是一个地址,指向栈里的一个局部变量然后弹出栈帧

int *ref_no_val()

{

 int num;

 return &num;

}

这里他已经不再指向一个合法的变量了.