Pointers to Pointers

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 is ip1,and **ipp is i, 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 of ip2,so that it ( ip1)now points at j:

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

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 variable i.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 the returnstatement.)The important thing to notice is that for the function to returna value of type int,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);
	else	fprintf(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 call malloc directly.A different,and more useful,approach to writing a ``wrapper'' function around mallocis 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.''Our allocstr 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;
			else	prevlp->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 the next 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 whose next pointer will need adjusting.(Notice that if we were to delete node 3,we would copy its next 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 the next pointers.(Of course, the payoff is not absolute,becausethe use of a pointer to a pointer to a struct 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 by lpp(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 updated to)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 by lpp.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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Q:'pointers to different types at argument' 的意思是什么? A:'pointers to different types at argument' 的意思是参数是指向不同类型的指针。 ### 回答2: 指向不同类型的参数指针是一种在C语言中使用广泛的编程技术,它允许函数接受不同类型的参数作为输入,并且能够对这些参数进行操作。 使用指向不同类型的参数指针,可以让函数更加通用,因为它可以接受不同类型的参数作为输入。这种技术的最基本的应用就是在函数的参数中使用void指针,它可以接受任何类型的指针作为输入。函数内部通过将void指针转换为实际的指针类型,可以进行对参数的操作。 除了使用void指针外,还可以通过使用强制类型转换将参数指针转换为不同的指针类型。这种方式可以在函数接受不同类型的指针参数时使用。例如,当一个函数需要同时接受int和float类型的指针时,可以使用强制类型转换将参数指针转换为相应的类型,然后进行对应的操作。 在使用指向不同类型的参数指针时,需要注意指针的类型与指向的数据类型一致,否则会产生未定义的行为。此外,由于指向不同类型的参数指针可能存在类型转换的操作,因此需要谨慎处理指针的类型,以避免出现错误和潜在的安全问题。 综上所述,指向不同类型的参数指针是一种非常有用的编程技术,可以让函数更加通用,并可以接受不同类型的参数作为输入。但在使用时需要注意指针的类型和类型转换的问题,以保证代码正确和安全。 ### 回答3: 指针是一种数据类型,它存储了一个内存地址,可以用该地址访问存储在该地址中的数据。指针类型的变量在C和C++中非常常见,因为它们可以帮助我们传递参数和操作内存。关于传递参数,指针可以指向不同类型的数据,包括整数、字符、浮点、结构体、甚至其他指针。 指针的类型定义是由它所指向的对象的类型决定的。例如,一个整型指针的类型可以定义为int *,这意味着它指向一个整型变量的地址。同样的,一个字符指针的类型可以定义为char *,它指向一个字符变量的地址。指针类型由编程人员显式地声明,这可以确保在编译时代码被正确的处理,同时也有利于代码的可读性和维护性。 在传递指针作为函数参数时,可以使用抽象的指针类型(void *),它可以指向任意类型的数据。使用这种抽象类型的指针,可以将不同类型的数据传递给函数,并让函数改变这些数据的值。然而,使用void指针类型需要谨慎,因为它不提供类型检查,可能导致运行时错误。 指针的另一个重要特性是指针运算。指针可以通过加、减或比较运算对内存地址进行操作。这是非常实用的,它允许我们操作结构体、数组和字符串。例如,可以使用指针对字符串进行搜索、排序和替换操作。 尽管指针可以非常灵活地指向不同类型的数据对象,但也需要非常小心的使用它们。指针可能会指向不存在的内存地址或垃圾内存,这可能导致程序崩溃或不确定的结果。对指针进行充分的验证和检查可以帮助确保程序的正确性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值