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

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

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

Как указатели на указатели работают в 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 

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

Мне нравится этот «реальный мир» пример кода указателя на использование указателя, в 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 , что означает, что мы достигли конца списка (строка 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 в начале. Простой и умный.

Кроме того, кто-то из этого streamа заметил, что причина в том, что это лучше, потому что *pp = entry->next является атомарным. Это, безусловно, НЕ атомный .
Вышеприведенное выражение содержит два оператора разыменования ( * и -> ) и одно назначение, и ни одна из этих трех вещей не является атомарной.
Это распространенное заблуждение, но, увы, почти ничего в C не должно считаться атомарным (в том числе операторы ++ и -- )!

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

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

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

«Долго, – сказал Рыцарь, – но это очень, очень красиво. Все, кто меня слышит, петь – либо он приносит слезы на глаза, либо … –

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

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

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

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

«Тогда я должен был сказать« Это то, что называется песней »?» Алиса поправила себя.

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

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

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

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

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

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

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

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

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

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

http://www.flippinbits.com Ссылка >>

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

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

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

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

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

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

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

Согласно рисунку, 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  int main () { int num = 10; int *ptr1; int **ptr2; // Take the address of var ptr1 = # // 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 

Указатель на указатель – это, скорее, указатель на указатель.

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

dpointer [5] [6]

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

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

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

 //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; // eg, newly allocated memory 0x2000 store 11 j = *i; j++; *j = 12; ; // eg, 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 

Я создал 5-минутное видео, в котором объясняется, как работают указатели:

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

указательные ведра

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

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

  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); 

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

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

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

  • Как преобразовать целое число в шестнадцатеричную строку в C?
  • Добавление или вычитание цвета из изображения в pictureBox с использованием C #
  • Поиск последовательной битовой строки 1 или 0
  • Почему я не должен использовать atoi ()?
  • Когда использовать бит-поля в C?
  • Равновесие и допуски с плавающей точкой
  • В частности, что опасно для кастования результата malloc?
  • Число округлений до 2 десятичных мест в C
  • Несколько символов в символьной константе
  • Почему sizeof (param_array) - размер указателя?
  • atoi () - строка в int
  • Interesting Posts

    Сделать mcrypt доступным для PHP 5.3 на Mac OS X 10.6

    Не удается удалить элемент «Не удалось найти этот элемент»

    Получение ошибки при попытке настроить общие папки на экземпляре Ubuntu VMWare Fusion, работающем на OSX

    Как разместить / использовать частный трекер

    Конфигурация источника данных для внешних источников Grails

    Как настроить настраиваемые обработчики URL-адресов в OS X?

    Почему общее использование памяти, сообщаемое диспетчером задач Windows, намного выше, чем сумма использования памяти всех процессов?

    Можете ли вы рекомендовать механизм шаблонов .net?

    Запретить использование Word из букв при копировании текста

    Как _really_ программно изменить основной и акцентный цвет в Android Lollipop?

    Как заменить сообщение об ошибке ModelState по умолчанию в Asp.net MVC 2?

    Ближайшие соседи по высокоразмерным данным?

    CMD cd на другие диски, кроме C: \ not working

    C # WebClient отключить кеш

    Android отключает тайм-аут экрана во время работы приложения

    Давайте будем гением компьютера.