考虑这个程序:
int main(void)
{
int* i = malloc(sizeof(int));
int* j = malloc(sizeof(int));
}
然而,这是一种幼稚的方法,因为malloc
可能会失败,并且指针不是free
'd。
所以你可以这样做:
int main(void)
{
int* i;
int* j;
if ((i = malloc(sizeof(int)) < 0)
{
return -1;
}
if ((j = malloc(sizeof(int)) < 0)
{
free(i);
return -1;
}
free(i);
free(j);
}
然而,我认为这很容易出错。考虑必须分配20个指针,在最后一个malloc
错误的情况下,必须分配free
19个变量,然后分配return -1
。
我也知道atexit
,它可以帮助我这样写:
int* i;
int* j;
void del_i(void)
{
free(i);
}
void del_j(void)
{
free(j);
}
int main(void)
{
if ((i = malloc(sizeof(int)) < 0)
{
return -1;
}
else
{
atexit(del_i);
}
if ((j = malloc(sizeof(int)) < 0)
{
return -1;
}
else
{
atexit(del_j);
}
}
这更好,但我不喜欢必须将所有指针声明为全局指针。有没有办法将这两种方法结合起来,基本上是:
- 具有指针的析构函数,可以直接执行,也可以与
atexit
一起使用 - 具有指向函数的本地指针
NULL
上的free
被定义为安全no-op。因此,一个非跳跃变化可能是:
int *i = malloc(sizeof(int));
int *j = malloc(sizeof(int));
if(i && j)
{
// do some work
}
free(i);
free(j);
首先,这将不会检测malloc
故障:
if ((i = malloc(sizeof(int)) < 0)
{
return -1;
}
malloc
在失败时返回NULL
,而不是负数。
其次,atexit
有利于清理静态和全局对象。仅在atexit
中使用局部对象来使其全局不是一个好主意。
一个更好的方法是为所有相关指针创建一个struct
,这些指针需要在一个要么全有要么全无的单元中分配,定义一个一次释放所有指针的函数,并编写一个函数来逐个分配指针,并在每次分配时进行内存检查:
typedef struct AllOrNothing {
double *dPtr;
int *iPtr;
float *fPtr;
size_t n;
} AllOrNothing;
void freeAllOrNothing(AllOrNothing *ptr) {
free(ptr->dPtr);
free(ptr->iPtr);
free(ptr->fPtr);
free(ptr);
}
int allocateAllOrNothing(size_t n, AllOrNothing **res) {
*res = malloc(sizeof(AllOrNothing));
if (*res == NULL) {
return -1;
}
// Freeing NULL is allowed by the standard.
// Set all pointers to NULL upfront, so we can free them
// regardless of the stage at which the allocation fails
(*res)->dPtr = NULL;
(*res)->iPtr = NULL;
(*res)->fPtr = NULL;
(*res)->n = n;
(*res)->dPtr = malloc(n*sizeof(double));
if ((*res)->dPtr == NULL) {
free(*res);
*res = NULL;
return -1;
}
(*res)->fPtr = malloc(n*sizeof(float));
if ((*res)->fPtr == NULL) {
free(*res);
*res = NULL;
return -1;
}
(*res)->iPtr = malloc(n*sizeof(int));
if ((*res)->iPtr == NULL) {
free(*res);
*res = NULL;
return -1;
}
return 0;
}
int main(void)
{
int* i = NULL; // Init with NULL otherwise free on none NULL possible
int* j = NULLL;
if (!(i = malloc(sizeof(int)))
{
goto exit;
}
if (!(j = malloc(sizeof(int)))
{
goto exit;
}
...
exit:
free(i);
free(j);
...
return err;
}
这是可以通过goto语句解决的问题。
int main(void)
{
int* i = NULL;
int* j = NULL;
bool success = false;
do {
i = malloc(sizeof(int));
if (NULL == i) break;
j = malloc(sizeof(int));
if (NULL == j) break;
success = true;
} while (0);
if (!success)
{
printf("Something failed!");
}
else
{
printf("All succeeded!");
// Do more work
}
free(i);
free(j);
return (success? 0 : 1);
}
避免多个出口点。避免交错分配和错误处理。遵循干净的操作顺序:
- 声明、分配和初始化资源
- 如果全部成功,就完成任务
- 清理
- 退货状态
// Do all allocations first, test their `NULL`-ness, then free them all.
int main(void) {
// Allocate resources
// declare and allocate in one step
int* i = malloc(sizeof *i);
double* j = malloc(sizeof *j);
// Test for acceptability
bool ok = i && j;
// Perform the main body of code
if (ok) {
; // do normal process in the code;
}
// free resources
free(i);
free(j);
// return status
return ok ? 0 : -1;
}
int *i=NULL,*j=NULL;
if(!(i=malloc(sizeof(int))))
goto EXIT;
if(!(j=malloc(sizeof(int))))
goto EXIT;
/* do some work */
return 0;
EXIT:
free(i);
free(j);
exit(EXIT_FAILURE);
尽管goto被认为是一种糟糕的编程实践但在这里,我们可以使用它轻松简单地完成任务