C语言程序中与内存有关的常见错误


在日常开发中,与内存有关的错误最令人烦恼。出现内存问题,程序经常在距离错误源一段距离之后才表现出来。下面列举并分析了与内存有关的几种错误:

一、间接引用非法指针

如果间接引用一个指向没有任何意义的数据的指针,那么操作系统会以段错误异常终止程序

例如:

scanf("%d", value);

这种情况下,scanf将把value内容认为是一个地址,并试图将一个整型数据写到这个位置。这会导致程序出现异常,立即终止。

二、操作未初始化的内存

int *func(int **A, int *x, int n)
{
    
    
    int i, j;
    int *p = (int *)malloc(n * sizeof(int));

    for(i = 0; i < n; i++)
    {
    
    
        for(j = 0; j < n; j++)
        {
    
    
            p[i] += A[i][j] * x[j]
        }
    }
    return y;
}

示例中新申请的内存地址(p指向的地址)并没有被初始化为零应该显式地将y[i]memset为零,或者使用calloc函数申请内存。

三、栈缓冲区溢出

void func()
{
    
    
    char buf[10];
    gets(buf);
    
    return;
}

这个函数没有检查输入字符串的大小就写入栈中目标缓冲区,会出现缓冲区溢出错误,因为gets函数只是简单复制一个任意长度的字符串到缓冲区,不限制输入串的大小。解决这个问题的方法是,可以用限制了输入串大小的fgets函数。

四、指针以及指针指向的对象大小不一导致堆溢出

常见的错误是,假设指向对象的指针和它们所指向的对象是相同大小的,示例程序:

int **makeArray(int n, int m)
{
    
    
    int i;
    int **A = (int **)malloc(n * sizeof(int));
    
    for(i = 0; i < n; i++)
    {
    
    
        A[j] = (int *)malloc(m * sizeof(int));
    }
    return A;
}

此程序的目的是创建一个由n个指针组成的数组,每个指针都指向一个包含m个int类型数据的数组。然而,然后程序在初始化中将sizeof(int *)写成了sizeof(int),代码实际上创建的是一个int的数组,而非设计程序时候设想的有n个int类型的指针。因此这段代码只有在int和指向int的指针大小相同的机器上运行良好,否则就会出现错误。

五、循环边界导致内存越界

int **makeArray(int n, int m)
{
    
    
    int i;
    int **A = (int **)malloc(n * sizeof(int *)); 

    for(i = 0; i <= n; i++) /* 注意循环终止条件 */
    {
    
    
        A[j] = (int *)malloc(m * sizeof(int));
    }
    return A;
}

程序循环最后一步,由于边界值不正确,会试图初始化数组的第n+1个元素,这个过程会覆盖A数组后面的某个内存位置。

六、操作符的优先级导致操作指针和指针指向的对象

如果不太注意C操作符的优先级和结合性,我们就会错误地操作指针或者指针所指向的对象。
例如:减少某个指针指向的整数的值的代码书写如下:

*ptr--;

然而,由于一元运算符“--”“*”的优先级相同,且从右向左结合。那么上述代码实际的效果为*(ptr--),即减少的是指针自己的值,移动了指针地址,而不是它所指向的整数的值。

修正后的代码如下:

(*ptr)--;

七、指针运算单位出错

指针的算术运算操作是指针指向的对象的大小为单位进行的,不一定一定是一个字节

例如,循环一个int类型数组,并返回一个val首次出现的指针:

int *func(int *p, int val)
{
    
    
    while(*p && *p != val)
    {
    
    
        p += sizeof(int);
    }
    return p;
}

如果是64位的机器,每次循环时,都把指针p加了4(一个int类型的字节数),而int类型指针是8个字节,这样p指针就移动到4个int数据后,不正确地扫描了数组中每4个整数

八、操作不在生命周期内的局部变量

int *func()
{
    
    
    int val;

    return &val;
}

这个函数返回一个指针(假设为ptr),指向栈里的一个局部变量,然后弹出它的栈帧。尽管ptr仍然指向一个合法的内存地址,但它已经不再指向一个合法的变量了。

以后在程序中调用其他函数时,内存将重用它们的栈帧。如果程序赋值给*ptr,那么它可能实际上正在修改另一个含的栈帧中的数据,从而潜在地带来灾难性的后果。

九、引用经被释放了的堆块中的数据

int *func(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]++;
    }

    return y;
}

在free(x)后操作x[i],其内容可能已经是某个其他已分配堆块的一部分了,导致程序运行结果与预期不符合,出现错误

十、内存泄漏

当开发时候不小心忘记释放已分配的内存块,而在堆里创建了垃圾时,会发生内存泄漏

void func(int n)
{
    
    
    int *x = (int *)malloc(n * sizeof(int));

    return;
}

如果经常调用这个函数,渐渐地堆里会充满了垃圾,造成内存泄漏。另外,有时也会引起程序终止或其他问题。

猜你喜欢

转载自blog.csdn.net/future_sky_word/article/details/131489284