指向指针的指针如何在C中工作?

时间:2009-05-22 11:12:10

标签: c

如何指针指针在C中工作? 你什么时候使用它们?

14 个答案:

答案 0 :(得分:341)

假设一台8位计算机具有8位地址(因此只有256字节的存储器)。这是该内存的一部分(顶部的数字是地址):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

你在这里看到的是,在地址63处字符串“hello”开始。所以在这种情况下,如果这是内存中唯一出现的“hello”,那么

const char *c = "hello";

...将c定义为指向(只读)字符串“hello”的指针,因此包含值63. c必须本身存储在某处:在上面的示例中在位置58.当然,我们不仅可以指向字符,还可以指向其他指针。 E.g:

const char **cp = &c;

现在cp指向c,也就是说,它包含c的地址(即58)。我们可以走得更远。考虑:

const char ***cpp = &cp;

现在cpp存储cp的地址。因此它具有值55(基于上面的示例),并且您猜对了:它本身存储在地址60处。


至于为什么使用指针指针:

  • 数组的名称通常会产生第一个元素的地址。因此,如果数组包含t类型的元素,则对该数组的引用具有类型t *。现在考虑类型为t的数组数组:当然,对这个2D数组的引用将具有类型(t *)* = t **,因此是指向指针的指针。
  • 尽管字符串数组听起来是一维的,但它实际上是二维的,因为字符串是字符数组。因此:char **
  • 如果要更改f类型的变量,函数t **将需要接受t *类型的参数。
  • 许多其他原因太多,无法在此列出。

答案 1 :(得分:41)

指针的指针如何在C中起作用?

首先,指针是一个变量,就像任何其他变量一样,但它保存变量的地址。

指向指针的指针是一个变量,就像任何其他变量一样,但它保存变量的地址。该变量恰好是一个指针。

你什么时候使用它们?

当您需要返回指向堆上某些内存的指针但不使用返回值时,可以使用它们。

示例:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

你这样称呼它:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

还有其他用途,例如每个C程序的main()参数都有一个指向argv指针的指针,其中每个元素都包含一个字符数组,这些字符是命令行选项。你必须小心,但是当你使用指针指针指向2维数组时,最好使用指向2维数组的指针。

为什么这很危险?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

以下是正确完成二维数组的指针示例:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

如果要为ROWS和COLUMNS支持可变数量的元素,则不能使用指向2维数组的指针。但是当你事先知道时,你会使用二维数组。

答案 2 :(得分:26)

我喜欢这个指向指针用法的“真实世界”代码示例,在Git 2.0中,commit 7b1004b

  

Linus曾说过:

     
    

我实际上希望更多人了解真正核心的低级编码。不是很大,很复杂的东西,如无锁名称查找,但只是很好地使用指针指针等     例如,我看到有太多人通过跟踪“prev”条目来删除单链接列表条目,然后删除条目,执行类似

的操作   
if (prev)
  prev->next = entry->next;
else
  list_head = entry->next;
  
    

每当我看到这样的代码时,我就会去“这个人不理解指针”。而且很遗憾很常见。

  
     

理解指针的人只需使用“指向条目指针的指针”,并使用list_head的地址初始化它。然后当他们遍历列表时,他们可以在不使用任何条件的情况下删除条目,只需执行

即可
*pp =  entry->next

http://i.stack.imgur.com/bpfxT.gif

  

应用这种简化使我们即使在添加2行注释时也会从此函数中丢失7行。

-   struct combine_diff_path *p, *pprev, *ptmp;
+   struct combine_diff_path *p, **tail = &curr;

Chris通过 in the comments 向2016年视频“Linus Torvalds's Double Pointer Problem ”指出Philip Buuck


kumar指出in the comments博文“Linus on Understanding Pointers”,其中 Grisha Trubetskoy 解释:

  

想象一下,您将链接列表定义为:

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;
  

您需要从头到尾迭代它并删除其值等于to_remove值的特定元素。
  更明显的方法是:

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) { /* line 4 */
    if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
        if (prev)
           prev->next = entry->next; /* remove the entry ; line 7 */
        else
            head = entry->next;      /* special case - first entry ; line 9 */

    /* move on to the next entry */
    prev = entry;
    entry = entry->next;
}
  

我们上面所做的是:

     
      
  • 迭代列表,直到输入为NULL,这意味着我们已到达列表的末尾(第4行)。
  •   
  • 当我们遇到要删除的条目时(第5行),      
        
    • 我们将当前下一个指针的值分配给前一个
    •   
    • 因此消除了当前元素(第7行)。
    •   
  •   
     

上面有一个特例 - 在迭代开始时没有前一个条目(prevNULL),所以要删除列表中的第一个条目,你必须修改头本身(第9行)。

     

Linus所说的是上面的代码可以通过使前一个元素成为指针指针而不仅仅是一个指针来简化。   然后代码如下所示:

list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry->val == to_remove)
        *pp = entry->next;

    pp = &entry->next;
    entry = entry->next;
}
  

上面的代码与之前的版本非常相似,但请注意我们不再需要关注列表第一个元素的特殊情况,因为pp不是NULL开始。简单而聪明。

     

此外,该线程中有人评论说这更好的原因是因为*pp = entry->next是原子的。 绝对不是原子   上面的表达式包含两个解引用运算符(*->)和一个赋值,这三个东西都不是原子的。
  这是一种常见的误解,但C中几乎没有任何内容可以假定为原子(包括++--运算符)!

答案 3 :(得分:13)

当涵盖大学编程课程的指针时,我们会得到两个关于如何开始学习它们的提示。第一个是查看Pointer Fun With Binky。第二个是考虑刘易斯卡罗尔的<{3>}段落通过镜子

  

“你很伤心,”骑士用一种焦虑的语气说:“让我唱一首歌来安慰你。”

     “这很长吗?”爱丽丝问道,因为那天她听到了很多诗歌。

     “这很长,”骑士说,“但它非常非常漂亮。每个听到我的人都会唱歌 - 无论是给他们的眼睛带来泪水,还是 - “

     

“或者是什么?”爱丽丝说道,因为骑士突然停顿了一下。

     

“或者它没有,你知道。这首歌的名字叫做'Haddocks'Eyes。'“

     

“哦,这就是这首歌的名字,是吗?”爱丽丝说,试着感兴趣。

     

“不,你不明白,”骑士说,看起来有些烦恼。 “这就是所谓的名字。这个名字真的是'老年人'。“

     

“然后我应该说'这就是这首歌的名字'?”爱丽丝纠正了自己。

     

“不,你不应该:那是另一回事!这首歌被称为'方式和手段':但这只是它的名字,你知道!“

     “那么,那首歌是什么?”爱丽丝说,他此时已经完全不知所措了。

     “我来了,”骑士说。 “这首歌真的是'A-Sitting On A Gate':这首曲子是我自己的发明。”

答案 4 :(得分:11)

您可能需要阅读此内容:Pointers to Pointers

希望这有助于澄清一些基本的疑虑。

答案 5 :(得分:7)

指向指针的指针也称为句柄。一种用法通常是在对象可以在内存中移动或移除时。一个人经常负责锁定和解锁对象的使用,因此在访问时不会移动它。

它经常用于内存受限环境,即Palm OS。

  

computer.howstuffworks.com Link>>

     

www.flippinbits.com Link>>

答案 6 :(得分:7)

当需要引用指针时。例如,当您希望修改在被调用函数内的调用函数范围内声明的指针变量的值(地址指向)时。

如果将单个指针作为参数传递,则将修改指针的本地副本,而不是调用范围中的原始指针。使用指针指针,您可以修改后者。

答案 7 :(得分:6)

考虑下面的图和程序,以便更好地理解这个概念

Double pointer diagram

根据图, ptr1 单指针,其地址为变量 num

ptr1 = &num;

类似地 ptr2 指向指针(双指针)的指针,指针的地址为 ptr1

ptr2 = &ptr1;

指向另一个指针的指针称为双指针。在此示例中, ptr2 是双指针。

上图中的值:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

示例:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

<强>输出:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

答案 8 :(得分:5)

您有一个包含某个地址的变量。那是一个指针。

然后你有另一个包含第一个变量地址的变量。那是指向指针的指针。

答案 9 :(得分:4)

它是指向指针地址值的指针。 (我知道这太可怕了)

基本上,它允许您传递指向另一个指针的地址值的指针,因此您可以修改另一个指针从子函数指向的位置,如:

void changeptr(int** pp)
{
  *pp=&someval;
}

答案 10 :(得分:3)

指向指针的指针就是指向指针的指针。

someType **的一个有意义的例子是一个二维数组:你有一个数组,填充了指向其他数组的指针,所以在你写的时候

dpointer [5] [6]

你在包含指向其第五个位置的其他数组的指针的数组中访问,获取指针(让fpointer他的名字),然后访问引用该数组的数组的第6个元素(所以,fpointer [6])。

答案 11 :(得分:1)

工作原理: 它是一个可以存储另一个指针的变量。

你什么时候使用它们: 如果你的函数想要构造一个数组并将它返回给调用者,那么很多用途就是其中之一。

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
    int *j;
    *i = (int*)malloc ( 2*sizeof(int) );
    **i = 11;  // e.g., newly allocated memory 0x2000 store 11
    j = *i;
    j++;
    *j = 12; ;  // e.g., newly allocated memory 0x2004 store 12

    return 2;
}

int main()
{
    int *i;
    int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
    for ( int j=0; j<n; j++ )
        printf( "roll no = %d \n", i[j] );

    return 0;
}

答案 12 :(得分:1)

我创建了一个5分钟的视频来解释指针是如何工作的:

<强> https://www.youtube.com/watch?v=3X-ray3tDjQ

pointer buckets

答案 13 :(得分:0)

有很多有用的解释,但我没有找到简短的描述,所以..

基本上指针是变量的地址。 简短摘要代码:

number = models.PositiveSmallIntegerField(
    default="", verbose_name="Match №:")

同样有用的信息可以在主题What means reference and dereference

中找到

而且我不太确定,什么时候可以指针有用,但一般来说,当你做一些manual/dynamic memory allocation- malloc, calloc, etc.时需要使用它们

所以我希望它也有助于澄清问题:)