将指针传递给函数时,传递的是值。如果我们想修改原指针而不是指针的副本,就需要传递指针的指针。在下例中,我们传递了一个整数数组的指针,为该数组分配内存并将其初始化。函数会用第一个参数返回分配的内存。在函数中,我们先分配内存,然后初始化。所分配的内存地址应该被赋给一个整数指针。为了在调用函数中修改这个指针,我们需要传入指针的地址。所以,参数被声明为int
指针的指针。在调用函数中,我们需要传递指针的地址:
void allocateArray(int **arr, int size, int value) {
*arr = (int*)malloc(size * sizeof(int));
if(*arr != NULL) {
for(int i=0; i<size; i++) {
*(*arr+i) = value;
}
}
}
这个函数可以用下面的代码测试:
int *vector = NULL;
allocateArray(&vector,5,45);
allocateArray
的第一个参数以整数指针的指针的形式传递。当我们调用这个函数时,需要传递这种类型的值。这是通过传递vector
地址做到的。malloc
返回的地址被赋给arr
。解引整数指针的指针得到的是整数指针。因为这是vector
的地址,所以我们修改了vector
。
内存分配说明如图3-7所示。左图显示malloc
返回且初始化数组后的栈状态。类似地,右图显示函数返回后的栈状态。
注意 要方便地发现内存泄漏这样的问题,只需画一张内存分配图。
下面这个版本的函数说明了为什么只传递一个指针不会起作用:
void allocateArray(int *arr, int size, int value) {
arr = (int*)malloc(size * sizeof(int));
if(arr != NULL) {
for(int i=0; i<size; i++) {
arr[i] = value;
}
}
}
下面的代码段说明了如何使用这个函数:
int *vector = NULL;
allocateArray(vector,5,45);
printf("%p\n",vector);
运行后会看到程序打印出0x0,因为将vector
传递给函数时,它的值被复制到了参数arr
中,修改arr
对vector
没有影响。当函数返回后,没有将存储在arr
中的值复制到vector
中。图3-8说明了内存分配情况:左图显示arr
被赋新值之前的内存状态;中图显示allocateArray
函数中的malloc
函数执行且初始化数组后的内存状态,arr
变量被修改为指向堆中的某个新位置;右图显示函数返回后程序栈的状态。此外,这里有内存泄漏,因为我们无法再访问地址600处的内存块了。
实现自己的free
函数
由于free
函数存在一些问题,因而某些程序员创建了自己的free
函数。free
函数不会检查传入的指针是否是NULL
,也不会在返回前把指针置为NULL
。释放指针之后将其置为NULL
是个好习惯。
有了3.2节中的基础知识,我们给出下面这个free
函数的实现,可以给指针赋NULL
。此处需要我们给它传递一个指针的指针:
void saferFree(void **pp) {
if (pp != NULL && *pp != NULL) {
free(*pp);
*pp = NULL;
}
}
saferFree
函数调用实际释放内存的free
函数,前者的参数声明为void
指针的指针。使用指针的指针允许我们修改传入的指针,而使用void
类型则可以传入所有类型的指针。不过,如果调用这个函数时没有显式地把指针类型转换为void
会产生警告,执行显式转换就不会有警告。
下面这个safeFree
宏调用saferFree
函数,执行类型转换,并使用了取地址操作符,这样就省去了函数使用者做类型转换和传递指针的地址:
#define safeFree(p) saferFree((void**)&(p))
下面的代码片段说明了这个宏的用法:
int main() {
int *pi;
pi = (int*) malloc(sizeof(int));
*pi = 5;
printf("Before: %p\n",pi);
safeFree(pi);
printf("After: %p\n",pi);
safeFree(pi);
return (EXIT_SUCCESS);
}
假设malloc
返回的内存位于地址1000,那么这段代码的输出是1000和0。第二次调用safeFree
宏给它传递NULL
值不会导致程序终止,因为saferFree
函数检测到这种情况并忽略了这个操作。