常见错误

指针的不正确使用通常是可能包含安全漏洞或程序崩溃的错误源,最常见的原因是分段错误。

不检查分配失败

内存分配不能保证成功,而是可以返回 NULL 指针。使用返回的值,而不检查分配是否成功,调用未定义的行为 。这通常会导致崩溃,但不能保证会发生崩溃,因此依赖它也会导致问题。

例如,不安全的方式:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

安全方式:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

在请求内存时使用文字数字而不是 sizeof

对于给定的编译器/机器配置,类型具有已知大小; 但是,没有任何标准可以定义给定类型(char 除外)的大小对于所有编译器/机器配置都是相同的。如果代码使用 4 代替 sizeof(int) 进行内存分配,它可能在原始机器上运行,但代码不一定可移植到其他机器或编译器。类型的固定尺寸应替换为 sizeof(that_type)sizeof(*var_ptr_to_that_type)

非便携式分配:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

便携式分配:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

或者,更好的是:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

内存泄漏

未能使用 free 取消分配内存会导致不可重用的内存累积,程序不再使用该内存; 这称为内存泄漏 。内存泄漏会浪费内存资源,并可能导致分配失败。

逻辑错误

所有分配必须遵循相同的模式:

  1. 使用 malloc(或 calloc)分配
  2. 用于存储数据
  3. 使用 free 进行解除分配

不遵守这种模式,例如在调用 free悬空指针 )之后或调用 malloc野指针 ) 之前使用内存,调用 free 两次(双重释放)等,通常会导致分段错误,导致程序崩溃。

这些错误可能是暂时的并且难以调试 - 例如,释放的内存通常不会被操作系统立即回收,因此悬挂指针可能会持续一段时间并且似乎可以正常工作。

在它运行的系统上, Valgrind 是一个非常有用的工具,用于识别泄漏的内存以及最初分配的位置。

创建指向堆栈变量的指针

创建指针不会延长指向的变量的生命周期。例如:

int* myFunction() 
{
    int x = 10;
    return &x;
}

这里,x 具有自动存储持续时间 (通常称为堆栈分配)。因为它是在堆栈上分配的,所以只要 myFunction 正在执行它的生命周期; 在 myFunction 退出后,变量 x 被破坏。此函数获取 x 的地址(使用 &x),并将其返回给调用者,使调用者留下指向不存在的变量的指针。然后,尝试访问此变量将调用未定义的行为

函数退出后,大多数编译器实际上并不清除堆栈帧,因此取消引用返回的指针通常会为你提供预期的数据。但是,当调用另一个函数时,指向的内存可能会被覆盖,并且看起来指向的数据已被破坏。

要解决这个问题,要么返回要返回的变量的存储,要么返回指向新创建的存储的指针,要么将有效指针传递给函数而不是返回一个,例如:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

递增/递减和解除引用

如果你写*p++来增加 p 指出的东西,那你错了。

在解除引用之前执行后递增/递减。因此,这个表达式将递增指针 p 本身并返回 p 指向的内容,然后在不改变它的情况下递增。

你应该写 (*p)++来增加 p 指向的东西。

这个规则也适用于后递减:*p-- 将递减指针 p 本身,而不是 p 所指向的。