Pointers to Pointers

来源:互联网 发布:java游戏编程入门 编辑:程序博客网 时间:2024/05/16 08:15

Chapter 22: Pointers to Pointers

Since we can have pointers to int,and pointers to char,and pointers to any structures we've defined,and in fact pointers to any type in C,it shouldn't come as too much of a surprise that we can havepointers to other pointers.If we're used to thinking about simple pointers,and to keeping clear in our minds the distinction betweenthe pointer itself andwhat it points to,we should be able to think about pointers to pointers, too,although we'll now have to distinguish between the pointer,what it points to,and what the pointer that it points to points to.(And, of course, we might also end up withpointers to pointers to pointers,or pointers to pointers to pointers to pointers,although these rapidly become too esoteric to have any practicaluse.)

The declaration of a pointer-to-pointer looks like

int **ipp;
where the two asterisks indicatethat two levels of pointers are involved.

Starting off with the familiar, uninspiring,kindergarten-style examples,we can demonstrate the use ofippby declaring some pointers for it to point to and someints for those pointers to point to:

int i = 5, j = 6; k = 7;int *ip1 = &i, *ip2 = &j;
Now we can set
ipp = &ip1;
and ipp points to ip1 which points to i.*ipp isip1,and**ipp isi, or 5.We can illustrate the situation,with our familiar box-and-arrow notation, like this:

If we say
*ipp = ip2;
we've changed the pointer pointed to by ipp(that is, ip1)to contain a copy ofip2,so that it (ip1)now points atj:

If we say
*ipp = &k;
we've changed the pointer pointed to by ipp(that is, ip1 again)to point tok:

What are pointers to pointers good for, in practice?One use is returning pointers from functions,via pointer arguments rather than as the formal return value.To explain this, let's first step back and consider the case ofreturning a simple type, such asint, from a function viaa pointer argument.If we write the function

f(int *ip){*ip = 5;}
and then call it like this:
int i;f(&i);
then f will ``return'' the value 5 by writingit to the location specified by the pointer passed by the caller;in this case, to the caller's variablei.A function might ``return'' values in this way if ithad multiple things to return,since a function can only have one formal return value(that is, it can only return one value via thereturnstatement.)The important thing to notice is that for the function to returna value of typeint,it used a parameter of type pointer-to-int.

Now, suppose that a function wants to return a pointer in this way.The corresponding parameter will then have to be a pointer to a pointer.For example, here is a little function which tries to allocatememory for a string of lengthn,and which returns zero (``false'') if it fails and 1(nonzero, or ``true'') if it succeeds, returning theactual pointer to the allocated memory via a pointer:

#include <stdlib.h>int allocstr(int len, char **retptr){char *p = malloc(len + 1);/* +1 for \0 */if(p == NULL)return 0;*retptr = p;return 1;}
The caller can then do something like
char *string = "Hello, world!";char *copystr;if(allocstr(strlen(string), &copystr))strcpy(copystr, string);elsefprintf(stderr, "out of memory\n");
(This is a fairly crude example;the allocstr function is not terribly useful.It would have been just about as easy for the caller to callmalloc directly.A different,and more useful,approach to writing a ``wrapper'' function aroundmallocis exemplified by the chkmalloc function we've been using.)

One side point about pointers to pointers and memory allocation:although the void * type,as returned by malloc,is a ``generic pointer,''suitable for assigning to or from pointers of any type,the hypothetical typevoid ** isnota ``generic pointer to pointer.''Ourallocstr example can only be used for allocatingpointers tochar.It would not be possible to use a function which returned genericpointers indirectly via avoid ** pointer,because when you tried to use it, for example by declaring andcalling

double *dptr;if(!hypotheticalwrapperfunc(100, sizeof(double), &dptr))fprintf(stderr, "out of memory\n");
you would not be passing a void **,but rather a double **.

Another good use for pointers to pointers is in dynamically allocated,simulated multidimensional arrays,which we'll discuss in the next chapter.

As a final example,let's look at how pointers to pointers can be used to eliminate anuisance we've had when trying to insert and delete items inlinked lists.For simplicity, we'll consider lists of integers,built using this structure:

struct list{int item;struct list *next;};
Suppose we're trying to writesome codeto delete a given integer from a list.The straightforward solution looks like this:
/* delete node containing i from list pointed to by lp */struct list *lp, *prevlp;for(lp = list; lp != NULL; lp = lp->next){if(lp->item == i){if(lp == list)list = lp->next;elseprevlp->next = lp->next;break;}prevlp = lp;}}
This code works, but it has two blemishes.One is that it has to use an extra variable to keep track of thenode one behind the one it's looking at,and the other is that it has to use an extra test to special-case thesituation in which the node being deleted is at the head of thelist.Both of these problems arise because the deletion of a node fromthe list involves modifying the previous pointer to point to the next node(that is, the node before the deleted node to point to the onefollowing).But, depending on whether the node being deleted is the firstnode in the list or not,the pointer that needs modifying is either the pointer thatpoints to the head of the list,or thenext pointer in the previous node.

To illustrate this, suppose that we have the list (1, 2, 3)and we're trying to delete the element 1.After we've found the element 1,lp points to its node,which just happens to be the same nodethat the mainlist pointer points to,as illustrated in (a) below:


To remove element 1 from the list,then,we must adjust the main list pointerso that it points to 2's node,the new head of the list(as shown in (b)).If we were trying to delete node 2, on the other hand(as illustrated in (c) above),we'd have to adjust node 1's next pointer to point to 3.The prevlp pointer keeps track ofthe previous node we were looking at,since(at other than the first node in the list)that's the node whosenext pointer will need adjusting.(Notice that if we were to delete node 3,we would copy itsnext pointer over to 2,but since 3's next pointer is the null pointer,copying it to node 2would make node 2 the end of the list,as desired.)

We can write another version of the list-deletion code,which is (in some ways, at least)much cleaner,by using apointer to a pointerto astruct list.This pointer will point at the pointer which points at the nodewe're looking at;it will either point at the head pointer or at the nextpointer of the node we looked at last time.Since this pointer points at the pointer that points at the nodewe're looking at (got that?),it points at the pointer which we need to modify if the nodewe're looking at is the node we're deleting.Let's see how the code looks:

struct list **lpp;for(lpp = &list; *lpp != NULL; lpp = &(*lpp)->next){if((*lpp)->item == i){*lpp = (*lpp)->next;break;}}}
That single line
*lpp = (*lpp)->next;
updates the correct pointer,to splice the nodeit refers toout of the list,regardless of whetherthe pointer being updated isthe head pointeror one of thenext pointers.(Of course, the payoff is not absolute,becausethe use of a pointer to a pointer to astruct list leadsto an algorithm which might not be nearly as obvious at first glance.)

To illustratethe use of the pointer-to-pointer lppgraphically,here are two more figures illustratingthe situation just before deleting node 1(on the left)or node 2(on the right).


In both cases,lpp points at a struct node pointerwhich points at the node to be deleted.In both cases,the pointer pointed to bylpp(that is, the pointer*lpp)is the pointer that needs to be updated.In both cases, the new pointer(the pointer that*lpp is to be updatedto)is the next pointer of the node being deleted,which is always(*lpp)->next.

One other aspect of the code deserves mention.The expression

(*lpp)->next
describes the next pointer of the struct nodewhich is pointed to by*lpp,that is,which is pointed to by the pointerwhich is pointed to bylpp.The expression
lpp = &(*lpp)->next
sets lpp to point to the next fieldof the struct listpointed to by*lpp.In both cases,the parenthesesaround*lppare needed because the precedence of* islower than->.

As a second,related example,here is a piece of code for inserting a new node into a list,in its proper order.This code uses a pointer-to-pointer-to-struct listfor the same reason,namely,so that it doesn't have to worry about treating the beginning ofthe list specially.

/* insert node newlp into list */struct list **lpp;for(lpp = &list; *lpp != NULL; lpp = &(*lpp)->next){struct list *lp = *lpp;if(newlp->item < lp->item){newlp->next = lp;*lpp = newlp;break;}}} 以Linux 2.6.34内核中fs/sysfs/dir.c中代码为例:
/** *sysfs_link_sibling - link sysfs_dirent into sibling list *@sd: sysfs_dirent of interest * *Link @sd into its sibling list which starts from *sd->s_parent->s_dir.children. * *Locking: *mutex_lock(sysfs_mutex) */static void sysfs_link_sibling(struct sysfs_dirent *sd){struct sysfs_dirent *parent_sd = sd->s_parent;struct sysfs_dirent **pos;BUG_ON(sd->s_sibling);/* Store directory entries in order by ino.  This allows * readdir to properly restart without having to add a * cursor into the s_dir.children list. */for (pos = &parent_sd->s_dir.children; *pos; pos = &(*pos)->s_sibling) {if (sd->s_ino < (*pos)->s_ino)break;}sd->s_sibling = *pos;*pos = sd;}/** *sysfs_unlink_sibling - unlink sysfs_dirent from sibling list *@sd: sysfs_dirent of interest * *Unlink @sd from its sibling list which starts from *sd->s_parent->s_dir.children. * *Locking: *mutex_lock(sysfs_mutex) */static void sysfs_unlink_sibling(struct sysfs_dirent *sd){struct sysfs_dirent **pos;for (pos = &sd->s_parent->s_dir.children; *pos;     pos = &(*pos)->s_sibling) {if (*pos == sd) {*pos = sd->s_sibling;sd->s_sibling = NULL;break;}}}


References:
https://www.eskimo.com/~scs/cclass/int/sx8.html
https://stackoverflow.com/questions/859634/c-pointer-to-array-array-of-pointers-disambiguation
https://stackoverflow.com/questions/35021521/what-does-do-in-c-language?noredirect=1
原创粉丝点击