常见错误
指针的不正确使用通常是可能包含安全漏洞或程序崩溃的错误源,最常见的原因是分段错误。
不检查分配失败
内存分配不能保证成功,而是可以返回 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
取消分配内存会导致不可重用的内存累积,程序不再使用该内存; 这称为内存泄漏 。内存泄漏会浪费内存资源,并可能导致分配失败。
逻辑错误
所有分配必须遵循相同的模式:
- 使用
malloc
(或calloc
)分配 - 用于存储数据
- 使用
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
所指向的。