Как указатель на указатели работает в C?

Как указатели на указатели работают на C? Когда вы их используете?

Ответ 1

Предположим, что 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 начинается строка "привет". Поэтому в этом случае, если это единственное появление "привет" в памяти, тогда

const char *c = "hello";

... определяет c как указатель на строку (только для чтения) "hello" и, следовательно, содержит значение 63. c должен быть где-то сохранен: в примере выше в местоположении 58. Конечно, мы можем не только указывать на персонажей, но и на другие указатели. Например:.

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 *.
  • Многие другие причины, которые слишком многочисленны, перечисляют здесь.

Ответ 2

Как указатели на указатели работают в 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 address in
//At this point x holds 5

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

Существуют и другие виды использования, так как аргумент main() каждой программы C имеет указатель на указатель на 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)
}

Вот пример указателя на 2-мерный массив, выполненный правильно:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

Вы не можете использовать указатель на 2-мерный массив, хотя, если вы хотите поддерживать переменное количество элементов для ROWS и COLUMNS. Но когда вы знаете перед рукой, вы должны использовать 2-мерный массив.

Ответ 3

Мне нравится этот "реальный мир" пример кода указателя на использование указателя, в Git 2.0, commit 7b1004b:

Линус однажды сказал:

Я на самом деле желаю, чтобы больше людей понимали действительно базовый низкоуровневый вид кодирования. Не большие, сложные вещи, такие как поиск без блокировки, но просто хорошее использование указателей для указателей и т.д.
Например, я видел слишком много людей, которые удаляют запись с односвязным списком, отслеживая запись "prev", а затем удаляют запись, делая что-то вроде

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

и всякий раз, когда я вижу такой код, я просто иду "Этот человек не понимает указателей". И это, к сожалению, довольно распространено.

Люди, которые понимают указатели, просто используют " указатель на указатель ввода " и инициализируют это с адресом list_head. И затем, когда они пересекают список, они могут удалить запись без каких-либо условностей, просто выполняя

*pp =  entry->next

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

Применение этого упрощения позволяет нам потерять 7 строк из этой функции, даже добавляя 2 строки комментария.

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

Крис указывает на комментарии к видеофильму 2016 года " Проблема двойных указателей Линуса Торвальдса " Филиппа Бука.


Кумар указывает в комментариях сообщение в блоге " Linus on Understanding Pointer ", где объясняет Гриша Трубецкой:

Представьте, что у вас есть связанный список, определенный как:

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, что означает, что weve достигнет конца списка (строка 4).
  • Когда мы сталкиваемся с записью, которую хотим удалить (строка 5),
    • мы присваиваем значение текущего следующего указателя предыдущему,
    • таким образом устраняя текущий элемент (строка 7).

Существует специальный случай выше - в начале итерации нет предыдущей записи (prev is NULL), и поэтому для удаления первой записи в списке вам нужно изменить сам заголовок (строка 9).

То, что сказал Линус, заключается в том, что приведенный выше код можно упростить, сделав предыдущий элемент указателем на указатель, а не только указателем.
Код выглядит следующим образом:

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 не должно считаться атомарным (в том числе операторы ++ и --)!

Ответ 4

Прикрывая указатели на курсе программирования в университете, нам дали два намека на то, как начать узнавать о них. Первым был просмотр Pointer Fun With Binky. Во-вторых, подумать о "Призрачные глаза" от Льюиса Кэрролла через зеркало.

"Вам грустно", - спросил Рыцарь тревожным тоном: "Позвольте мне спеть вам песню, чтобы утешить вас".

"Это очень долго?" - спросила Алиса, потому что в тот день она услышала много стихов.

"Это долго, - сказал Рыцарь, - но это очень, очень красиво. Все, кто слышит, как я пью его - либо он приносит слезы на глаза, либо еще..."

"Или что еще?" - сказала Алиса, потому что Рыцарь внезапно замолчал.

"Или это не так, вы знаете. Название песни называется" Глаза Чаддоков ".

" О, это название песни? - сказала Алиса, пытаясь почувствовать интерес.

"Нет, ты не понимаешь", - сказал Рыцарь, немного расстроенный. "Это то, что называется именем. На самом деле это" Человек в возрасте ".

" Тогда мне следовало сказать: "Что называется песней?" Алиса поправила себя.

"Нет, вы не должны: это совсем другое! Песня называется" Пути и средства ", но это только то, что она называла, вы знаете!"

"Ну, что же это за песня?" сказала Алиса, которая к этому времени была полностью сбита с толку.

"Я подошел к этому, - сказал Рыцарь." Песня действительно "A-Sitting On A Gate" и настройка моего собственного изобретения".

Ответ 5

Вы можете прочитать следующее: Указатели на указатели

Надеюсь, это поможет прояснить некоторые основные сомнения.

Ответ 6

Если требуется ссылка на указатель. Например, если вы хотите изменить значение (адрес, на которое указывает) переменной указателя, объявленной в области вызывающей функции внутри вызываемой функции.

Если вы передаете один указатель в качестве аргумента, вы будете изменять локальные копии указателя, а не оригинальный указатель в области вызова. С указателем на указатель вы измените последнее.

Ответ 7

Указатель на указатель также называется дескриптором. Одно использование для него часто происходит, когда объект можно перемещать в памяти или удалять. Один из них часто несет ответственность за блокировку и разблокирование использования объекта, чтобы он не перемещался при доступе к нему.

Он часто используется в среде с ограниченной памятью, то есть в Palm OS.

computer.howstuffworks.com Ссылка " >

www.flippinbits.com Ссылка " >

Ответ 8

У вас есть переменная, которая содержит адрес чего-то. Это указатель.

Затем у вас есть другая переменная, содержащая адрес первой переменной. Это указатель на указатель.

Ответ 9

это указатель на значение адреса указателя. (что ужасно знаю)

в основном, он позволяет передавать указатель на значение адреса другого указателя, поэтому вы можете изменить, где указывается другой указатель из подфункции, например:

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

Ответ 10

Рассмотрим приведенный ниже рисунок и программу, чтобы лучше понять эту концепцию.

Диаграмма двойного указателя

В соответствии с рисунком ptr1 является единственным указателем, который имеет адрес переменной num.

ptr1 = #

Аналогично 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

Ответ 11

Указатель на указатель - это, ну, указатель на указатель.

Значимым примером someType ** является двумерный массив: у вас есть один массив, заполненный указателями на другие массивы, поэтому, когда вы пишете

dpointer [5] [6]

вы получаете доступ к массиву, содержащему указатели на другие массивы в его 5-й позиции, получите указатель (пусть он назовет его имя), а затем получите доступ к 6-му элементу массива, на который ссылается этот массив (так, fpointer [6]).

Ответ 12

Как это работает: Это переменная, которая может хранить другой указатель.

Когда вы будете использовать их: Многие используют один из них, если ваша функция хочет построить массив и вернуть его вызывающему.

//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;
}

Ответ 14

Там так много полезных объяснений, но я не нашел просто краткое описание, поэтому..

В основном указатель - это адрес переменной. Код короткой сводки:

     int a, *p_a;//declaration of normal variable and int pointer variable
     a = 56;     //simply assign value
     p_a = &a;   //save address of "a" to pointer variable
     *p_a = 15;  //override the value of the variable

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference)
     printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

Также полезную информацию можно найти в разделе Что означает ссылка и разыменование

И я не уверен, когда могут быть указатели полезными, но в общем случае их необходимо использовать, когда вы выполняете ручную/динамическую память allocation- malloc, calloc и т.д.

Поэтому я надеюсь, что это также поможет прояснить проблему :)