Пожалуйста, укажите пример с объяснением.
Что означает "разыменование" указателя?
Ответ 1
Пересмотр базовой терминологии
Обычно он достаточно хорош - если вы не программируете сборку - чтобы предусмотреть указатель, содержащий числовой адрес памяти, причем 1 ссылается на второй байт в памяти процесса, 2 - на третий, 3 - на четвертый и т. Д....
- Что случилось с 0 и первым байтом? Хорошо, мы доберемся до этого - см. Нулевые указатели ниже.
- Для более точного определения того, какие хранилища указателей и как связаны память и адреса, см. "Больше о адресах памяти и почему вам, вероятно, не нужно знать".
Когда вы хотите получить доступ к данным/значению в памяти, на которую указывает указатель, - содержимое адреса с этим числовым индексом - тогда вы разыщите указатель.
У разных языков компьютера есть разные обозначения, чтобы сообщить компилятору или интерпретатору, что вы теперь заинтересованы в указанном значении - я фокусируюсь ниже на C и C++.
Сценарий указателя
Рассмотрим в C, указав такой указатель, как p
ниже...
const char* p = "abc";
... четыре байта с численными значениями, используемыми для кодирования букв "a", "b", "c" и 0 байт для обозначения конца текстовых данных, хранятся где-то в памяти, а числовой адрес этого данные хранятся в p
.
Например, если строковый литерал оказался с адресом 0x1000 и p
32-разрядным указателем в 0x2000, то содержимое памяти будет:
Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
Обратите внимание, что нет имени/идентификатора переменной для адреса 0x1000, но мы можем косвенно ссылаться на строковый литерал с помощью указателя, хранящего его адрес: p
.
Разделение указателя
Чтобы ссылаться на символы p
указывает на, мы разыскиваем p
помощью одной из этих обозначений (опять же для C):
assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
Вы также можете перемещать указатели по заостренным данным, разыгрывая их по ходу:
++p; // Increment p so it now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
Если у вас есть данные, которые могут быть записаны, вы можете сделать следующее:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
Вы должны знать во время компиляции, что вам понадобится переменная с именем x
, и код просит компилятор организовать, где он должен быть сохранен, чтобы адрес был доступен через &x
.
Выделение прав доступа и доступа к члену данных структуры
В C, если у вас есть переменная, являющаяся указателем на структуру с элементами данных, вы можете получить доступ к этим членам, используя оператор ->
разыменования:
typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
Многобайтовые типы данных
Чтобы использовать указатель, компьютерная программа также нуждается в некотором понимании типа данных, на которые указывает - если этому типу данных требуется более одного байта для представления, тогда указатель обычно указывает на младший байт в данных.
Итак, глядя на несколько более сложный пример:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
assert(++p); // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note: earlier ++p and + 2 here => sizes[3]
Указатели на динамически распределенную память
Иногда вы не знаете, сколько памяти вам потребуется, пока ваша программа не будет запущена, и не увидит, какие данные будут на нее наброшены... тогда вы можете динамически распределять память с помощью malloc
. Обычно хранить адрес в указателе...
int* p = malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
В C++ распределение памяти обычно выполняется с помощью new
оператора и освобождение с delete
:
int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
См. Также C++ интеллектуальные указатели ниже.
Потеря и утечка адресов
Часто указатель может быть единственным признаком того, где в памяти хранятся некоторые данные или буфер. Если требуется постоянное использование этих данных/буфера или возможность вызова free()
или delete
чтобы избежать утечки памяти, тогда программист должен работать с копией указателя...
const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
... или тщательно спланировать отмену любых изменений...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
C++ умные указатели
В C++ лучше всего использовать объекты интеллектуального указателя для хранения указателей и управления ими, автоматически освобождая их при запуске деструкторов интеллектуальных указателей. Так как C++ 11 стандартная библиотека предоставляет два unique_ptr
когда один владелец для выделенного объекта...
{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p destructor guaranteed to run "here", calling delete
... и shared_ptr
для владения акциями (используя подсчет ссылок)...
{
std::shared_ptr<T> p(new T(3.14, "pi"));
number_storage.may_add(p); // Might copy p into its container
} // p destructor will only delete the T if number_storage didn't copy
Нулевые указатели
В С, NULL
, и 0
- и, кроме того, в C++ nullptr
- может быть использован, чтобы указать, что указатель на данный момент не содержать адрес памяти переменной, и не должны быть разыменовываются или использоваться в арифметике с указателями. Например:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
char c;
while ((c = getopt(argc, argv, "f:")) != EOF)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
В C и C++, так же как встроенные числовые типы необязательно по умолчанию bools
0
, а bools
- false
, указатели не всегда равны NULL
. Все они установлены в 0/false/NULL, если они являются static
или только (CN10), прямыми или косвенными переменными-членами статических объектов или их базой или подвергаются нулевой инициализации (например, new T();
и new T(x, y, z);
выполнить нулевую инициализацию на T-членах, включая указатели, тогда как new T;
не).
Кроме того, когда вы назначаете 0
, NULL
и nullptr
на указатель, биты в указателе необязательно все сбрасываются: указатель может не содержать "0" на аппаратном уровне или ссылаться на адрес 0 в вашем виртуальном адресном пространстве. Компилятору разрешено хранить там что-то еще, если у него есть причина, но что бы он ни делал - если вы придете и сравните указатель с 0
, NULL
, nullptr
или другим указателем, которому было присвоено какое-либо из них, сравнение должно работать как ожидалось, Итак, ниже исходного кода на уровне компилятора "NULL" потенциально немного "волшебным" на языках C и C++...
Больше о адресах памяти, и почему вам, вероятно, не нужно знать
Более строго, инициализированные указатели сохраняют бит-шаблон, идентифицирующий либо NULL
либо (часто виртуальный) адрес памяти.
Простым случаем является то, что это числовое смещение во всем виртуальном адресном пространстве процесса; в более сложных случаях указатель может относиться к какой-либо определенной области памяти, которую ЦП может выбирать на основе регистров центрального процессора или какого-либо идентификатора сегмента, закодированного в битовой схеме, и/или в разных местах в зависимости от используя код компьютера.
Например, int*
правильно инициализированный, чтобы указывать на переменную int
может - после кастинга на float*
- получить доступ к значению в памяти "GPU", совершенно отличному от переменной int
, а затем после нажатия на указатель функции может ссылаться на различную память удерживая опкод машины для функции.
Языки программирования 3GL, такие как C и C++, как правило, скрывают эту сложность, так что:
-
Если компилятор дает вам указатель на переменную или функцию, вы можете разыменовать его свободно (до тех пор, пока переменная не разрушена/освобождена между тем), и это проблема компилятора, будь то конкретный регистр ЦП, необходимо восстановить заранее или инструкция машинного кода
-
Если вы получаете указатель на элемент в массиве, вы можете использовать арифметику указателя для перемещения в другом месте в массиве или даже для формирования адреса, который один за концом массива, который имеет право сравнивать с другими указателями на элементы в массиве (или которые аналогичным образом были перемещены с помощью арифметики указателя в одно и то же значение "один за прошлым"); снова в C и C++, это до компилятора, чтобы гарантировать, что это "просто работает",
-
Конкретные функции ОС, например, совместное использование памяти, могут давать вам указатели, и они будут "просто работать" в пределах диапазона адресов, который имеет для них смысл
-
Попытки перенести юридические указатели за эти границы или на произвольные числа на указатели или использовать указатели, отнесенные к несвязанным типам, обычно имеют неопределенное поведение, поэтому их следует избегать в библиотеках и приложениях более высокого уровня, но код для ОС, драйверов устройств и т.д. возможно, придется полагаться на поведение, оставленное неопределенным с помощью C или C++, которое, тем не менее, хорошо определено их конкретным оборудованием.
Ответ 2
Выделение указателя означает получение значения, которое хранится в ячейке памяти, указанной указателем. Оператор * используется для этого и называется оператором разыменования.
int a = 10;
int* ptr = &a;
printf("%d", *ptr); // With *ptr I'm dereferencing the pointer.
// Which means, I am asking the value pointed at by the pointer.
// ptr is pointing to the location in memory of the variable a.
// In a location, we have 10. So, dereferencing gives this value.
// Since we have indirect control over a location, we can modify its content using the pointer. This is an indirect way to access a.
*ptr = 20; // Now a content is no longer 10, and has been modified to 20.
Ответ 3
Указатель - это "ссылка" на значение. Подобно номеру вызова библиотеки, это ссылка на книгу. "Разыменование" номер вызова физически проходит и извлекает эту книгу.
int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
// The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4..
Если книги там нет, библиотекарь начинает кричать, закрывает библиотеку, и пара людей собирается расследовать причину того, что человек найдет книгу, которой ее нет.
Ответ 4
В простых словах разыменование означает доступ к значению из определенного места памяти, на которое указывает этот указатель.
Ответ 5
Код и объяснение из Основы указателя:
Операция разыменования начинается с указатель и следует за его стрелкой над для доступа к его указателю. Цель может быть посмотреть состояние плацдарма или измените состояние плацдарма. операция разыменования указателя работает только в том случае, если указатель имеет pointee - точка должна быть выделено и указатель должен быть установлен указать на это. Наиболее распространенная ошибка в коде указателя забывается установить вверх по пункту. Самый распространенный из-за этой ошибки в код является неудачным разыменованием операция. В Java неверный разыменовать вежливо по системе времени выполнения. В скомпилированном такие языки, как C, С++ и Pascal, неправильное разыменование иногда сбой и другие времена поврежденная память в некоторых тонких, случайных путь. Ошибки указателя в скомпилированном языки могут быть трудно отследить по этой причине.
void main() {
int* x; // Allocate the pointer x
x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it
*x = 42; // Dereference x to store 42 in its pointee
}
Ответ 6
Я думаю, что все предыдущие ответы ошибочны, поскольку они что разыменование означает доступ к фактической стоимости. Вместо этого Википедия дает правильное определение: https://en.wikipedia.org/wiki/Dereference_operator
Он работает с переменной указателя и возвращает значение l, эквивалентное значению на адрес указателя. Это называется "разыменованием" указателя.
Тем не менее, мы можем разыменовать указатель без доступ к значению, на которое он указывает. Например:
char *p = NULL;
*p;
Мы разыменовали указатель NULL без доступа к его стоимость. Или мы могли бы сделать:
p1 = &(*p);
sz = sizeof(*p);
Опять же, разыменование, но никогда не доступ к значению. Такой код НЕ сбой: Авария происходит, когда вы фактически получаете доступ к данным посредством неверный указатель. Однако, к сожалению, согласно стандартным разыменованием недопустимого указателя является undefined (за некоторыми исключениями), даже если вы не пытаетесь коснитесь фактических данных.
Короче говоря, разыменование указателя означает применение оператор разыменования. Этот оператор просто возвращает l-значение для вашего будущего использования.