1. 间接引用坏指针

在进程的虚拟地址空间中有很多区域是还没有映射的,如果试图向这些地址进行写或读,可能会引发保护异常或段错误。

一个最典型的例子:假设我们使用scanf函数从键盘输入一个值给变量val。

正确的写法是:

scanf("%d", &val);

然而,我们很多初级开发者容易错写成:

scanf("%d", val);

此时,编译器将val值错误的解释成一个地址值,并试图向这个地址写入。最好的情况,程序立即异常终止;坏的情况,val指向内存中某个合法的可写区域,并覆盖掉原来的值,这通常会在一段时间后造成难以察觉的灾难性后果。

  1. 读未初始化的内存

    虽然未赋值的全局变量会在编译时被统一安排到.bss段并被赋值为0,但对于堆(heap)内存却不是这样的。一个常见的错误就是假设堆内存被初始为0。

    int *matvec(int **A, int *x, int n)
    {
         
        int i,j;
        int *y = (int *)malloc(n * sizeof(int));
        for(i=0; i<n; i++)
    		 for(j=0; j<n; j++)
                 y[i] += A[i][j] * x[j];
        return y;
    }
    • 上述程序有两个错误,一是,没有考虑malloc分配失败的情况;二是没有初始化向量y为0。为了解决上述问题,可以使用realloc函数代替malloc,前者分配完之后默认初始化为0;
  2. 允许栈缓冲区溢出

    如果一个程序不检查输入串的大小就写入栈中的目标缓冲区,那么这个程序就会有缓冲区溢出错误。

    void bufoverflow()
    {
         
        char buf[64];
        gets(buf);	
        return;
    }
    • gets函数从键盘接收字符串并存到其参数buf指向的内存位置,但它从来不检查字符串长度有没有超过buf数组的长度。
    • 可以使用fgets函数代替它,因为前者具有一个参数限制接收字符的个数。
  3. 错位(off-by-one)错误

    它也是一种常见的造成覆盖错误的来源:

    int **makeArray2(int n, int m)
    {
         
    	int i;
    	int **A = (int **)malloc(n * sizeof(int *));
    	
    	for(i=0; i<=n; i++)		//此处有bug,应把<=改成<。
    		A[i] = (int *)malloc(m * sizeof(int));
    	return A;
    }

    以上程序由于错误的把<写成了<=,进而导致意外覆盖了数组边界后面一个元素。

  4. 错误的引用指针

    如果不注意C操作符的优先级和综合性,我们就会错误地操作指针,而不是指针所指向的对象。

    int *binheapdelete(int **binheap, int *size)
    {
         
        int *packet = binheap[0];
        
        binheap[0] = binheap[*size -1];
        *size--;			//此处有bug,应该改为(*size)--
        heapify(binheap, *size,0 );
        return(packet);
    }
    • bug行的原意是(*size)–,但程序员往往忽略了结合性。此时,保险起见建议还是用括号来明确优先级顺序。
  5. 误解指针运算

    一种常见的错误就是忘记了指针的算术操作是以它们指向对象的大小为单位的。

    int *search(int *p, int val)
    {
         
        while(*p && *p != val)
            p += sizeof(int);	//此处有bug。应该是p++
        return p;
    }
  6. 引用已释放的指针

    一个相似的错误是引用已经被释放了的堆块中的数据。下例中分配了一个整数数组x,之后释放了它,而在程序最后又再次引用了它。

    int *heapref(int n, int m)
    {
         
        int i;
        int *x,*y;
        x  = (int *)malloc(n * sizeof(int));
        .
        .
        .
        free(x);
        y  = (int *)malloc(m * sizeof(int));
        for(i=0; i<m; i++)
            y[i] = x[i]++;		//此处有bug,此时的x已经被释放
        return y;
    }
  7. 引用已释放的指针

没有太多经验的程序员不理解栈的规则,有时会引用不再合法的本地变量。

int *stackref()
{
   
    int val;
    return &val;
}

该函数返回一个指针,指向栈里的一个局部变量,然后释放它的栈空间。尽管p还是指向一个合法的地址,但是它已经不再指向一个合法的变量了。因为变量val在函数返回后已经被释放了。

  1. 内存泄漏

内存泄漏是缓慢、隐性的杀手,当程序员不小心忘记释放已分配块,而在堆里创建了垃圾时,会发生这种问题。

void leak(int n)
{
   
    int *x = (int *)malloc(n * sizeof(int));
    return;	//x被搞成了垃圾
}
  • 如果经常调用leak,那么渐渐地堆里就会充满了垃圾,最糟糕的情况下会占用整个虚拟地址空间。

获取更多知识,请点击关注:
嵌入式Linux&ARM
CSDN博客
简书博客