Как указатели на указатели работают на 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
Применение этого упрощения позволяет нам потерять 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
isNULL
), и поэтому для удаления первой записи в списке вам нужно изменить сам заголовок (строка 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.
Ответ 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 = #
// 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;
}
Ответ 13
Я создал 5-минутное видео, в котором объясняется, как работают указатели:
Ответ 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 и т.д.
Поэтому я надеюсь, что это также поможет прояснить проблему :)