两层指针也是指针,同样可以表示传入参数、传出参数或者Value-result参数,只不过该参数所指的内存空间应该解释成一个指针变量。用两层指针做传出参数的系统函数也很常见,比如pthread_join(3)
的void **
参数。下面看一个简单的例子。
/* redirect_ptr.h */ #ifndef REDIRECT_PTR_H #define REDIRECT_PTR_H extern void get_a_day(const char **); #endif
想一想,这里的参数指针是const char **
,有const
限定符,却不是传入参数而是传出参数,为什么?如果是传入参数应该怎么表示?
/* redirect_ptr.c */ #include "redirect_ptr.h" static const char *msg[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; void get_a_day(const char **pp) { static int i = 0; *pp = msg[i%7]; i++; }
/* main.c */ #include <stdio.h> #include "redirect_ptr.h" int main(void) { const char *firstday = NULL; const char *secondday = NULL; get_a_day(&firstday); get_a_day(&secondday); printf("%s\t%s\n", firstday, secondday); return 0; }
两层指针作为传出参数还有一种特别的用法,可以在函数中分配内存,调用者通过传出参数取得指向该内存的指针,比如getaddrinfo(3)
的struct addrinfo **
参数。一般来说,实现一个分配内存的函数就要实现一个释放内存的函数,所以getaddrinfo(3)
有一个对应的freeaddrinfo(3)
函数。
表 24.4. 通过参数分配内存示例:void alloc_unit(unit_t **pp);
void free_unit(unit_t *p);
调用者 | 实现者 |
---|---|
|
|
/* para_allocator.h */ #ifndef PARA_ALLOCATOR_H #define PARA_ALLOCATOR_H typedef struct { int number; char *msg; } unit_t; extern void alloc_unit(unit_t **); extern void free_unit(unit_t *); #endif
/* para_allocator.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "para_allocator.h" void alloc_unit(unit_t **pp) { unit_t *p = malloc(sizeof(unit_t)); if(p == NULL) { printf("out of memory\n"); exit(1); } p->number = 3; p->msg = malloc(20); strcpy(p->msg, "Hello World!"); *pp = p; } void free_unit(unit_t *p) { free(p->msg); free(p); }
/* main.c */ #include <stdio.h> #include "para_allocator.h" int main(void) { unit_t *p = NULL; alloc_unit(&p); printf("number: %d\nmsg: %s\n", p->number, p->msg); free_unit(p); p = NULL; return 0; }
思考一下,为什么在main
函数中不能直接调用free(p)
释放内存,而要调用free_unit(p)
?为什么一层指针的函数接口void alloc_unit(unit_t *p);
不能分配内存,而一定要用两层指针的函数接口?
总结一下,两层指针参数如果是传出的,可以有两种情况:第一种情况,传出的指针指向静态内存(比如上面的例子),或者指向已分配的动态内存(比如指向某个链表的节点);第二种情况是在函数中动态分配内存,然后传出的指针指向这块内存空间,这种情况下调用者应该在使用内存之后调用释放内存的函数,调用者的责任是请求分配和请求释放内存,实现者的责任是完成分配内存和释放内存的操作。由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况。