что означает «разыменование» указателя?

<p>пожалуйста, включите пример с объяснением.</p>

<h2>обзор основной терминологии</h2><p><em> обычно </em> достаточно хорошо - если вы не программируете на assembly - чтобы предусмотреть <strong> <em> указатель </em> </strong> содержащий числовой адрес памяти, где 1 относится ко второму байту в памяти процесса, 2 - к третьему, 3 - к четвертому и т. д.</p><ul> <li>что случилось с 0 и первым байтом? что ж, мы вернемся к этому позже - см. <em> нулевые указатели </em> ниже.</li> <li>для более точного определения того, что хранят указатели, и как связаны память и адреса, см. <em> "подробнее об адресах памяти и почему вам, вероятно, не нужно знать" </em>.</li> </ul><p>если вы хотите получить доступ к данным/значению в памяти, на которую указывает указатель - к содержанию адреса с этим числовым индексом - тогда вы <strong> <em> разыменовываете </em> </strong> указатель.</p><p>разные компьютерные языки имеют разные нотации, чтобы сообщить компилятору или интерпретатору, что вы сейчас заинтересованы в указанном значении - ниже я остановлюсь на c и c++.</p><h2>сценарий указателя</h2><p>рассмотрим в c, учитывая указатель, такой как <code>p</code> ниже ...</p><pre><code>const char* p = "abc"; </code></pre><p>... четыре байта с числовыми значениями, используемыми для кодирования букв "a", "b", "c" и 0 байтов для обозначения конца текстовых данных, хранятся где-то в памяти, а числовой адрес этого данные хранятся в <code>p</code>.</p><p>например, если строковый литерал оказался по адресу 0x1000 и <code>p</code> 32-битным указателем в 0x2000, содержимое памяти будет:</p><pre><code>memory address (hex) variable name contents 1000 'a' == 97 (ascii) 1001 'b' == 98 1002 'c' == 99 1003 0 ... 2000-2003 p 1000 hex </code></pre><p>обратите внимание, что для адреса 0x1000 нет имени/идентификатора переменной, но мы можем косвенно ссылаться на строковый литерал, используя указатель, хранящий его адрес: <code>p</code>.</p><h2>разыменование указателя</h2><p>чтобы сослаться на символы, на которые указывает <code>p</code>, мы разыменовываем <code>p</code>, используя одну из этих нотаций (опять же, для c):</p><pre><code>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] </code></pre><p>вы также можете перемещать указатели по указанным данным, разыменовывая их по мере продвижения:</p><pre><code>++p; // increment p so it's now 0x1001 assert(*p == 'b'); // p == 0x1001 which is where the 'b' is... </code></pre><p>если у вас есть данные, в которые можно записать данные, вы можете сделать следующее:</p><pre><code>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 </code></pre><p>выше вы должны были знать, что во время компиляции вам понадобится переменная с именем <code>x</code>, и код просит компилятор указать, где он должен храниться, чтобы адрес был доступен через <code>&x</code>.</p><h2>разыменование и доступ к элементу данных структуры</h2><p>в c, если у вас есть переменная, которая является указателем на структуру с элементами данных, вы можете получить доступ к этим элементам с помощью оператора разыменования <code>-></code>:</p><pre><code>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\_ </code></pre><h2>многобайтовые типы данных</h2><p>чтобы использовать указатель, компьютерной программе также необходимо получить представление о типе данных, на которые он указывает - если для этого типа данных требуется более одного байта, то указатель обычно указывает на байт с наименьшим номером в данных.</p><p>итак, рассмотрим чуть более сложный пример:</p><pre><code>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] </code></pre><h2>указатели на динамически выделяемую память</h2><p>иногда вы не знаете, сколько памяти вам понадобится, пока ваша программа не запустится и не увидит, какие данные выбрасываются в нее ... тогда вы можете динамически распределять память, используя <code>malloc</code>. это обычная практика хранить адрес в указателе ...</p><pre><code>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 </code></pre><p>в c++ выделение памяти обычно выполняется с помощью оператора <code>new</code>, а освобождение с помощью <code>delete</code>:</p><pre><code>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; </code></pre><p>см. также <em> интеллектуальные указатели c++ </em> ниже.</p><h2>потеря и утечка адресов</h2><p>часто указатель может быть единственным указанием того, где некоторые данные или буфер существуют в памяти. если требуется постоянное использование этих данных/буфера или возможность вызова <code>free()</code> или <code>delete</code>, чтобы избежать утечки памяти, то программист должен работать с копией указателя ...</p><pre><code>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); </code></pre><p>... или тщательно организовать аннулирование любых изменений ...</p><pre><code>const size\_t n = ...; p += n; ... p -= n; // restore earlier value... </code></pre><h2>c++ умные указатели</h2><p>в c++ рекомендуется использовать объекты <a href="http://en.wikipedia.org/wiki/smart\_pointer">умный указатель</a> для хранения указателей и управления ими, автоматически освобождая их при запуске деструкторов умных указателей. начиная с c++ 11 стандартная библиотека предоставляет два, <a href="http://en.cppreference.com/w/cpp/memory/unique\_ptr"><code>unique\_ptr</code></a> для случая, когда для выделенного объекта есть один владелец ...</p><pre><code>{ 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's destructor's guaranteed to run "here", calling delete </code></pre><p>... и <a href="http://en.cppreference.com/w/cpp/memory/shared\_ptr"><code>shared\_ptr</code></a> для владения акциями (используя <a href="http://en.wikipedia.org/wiki/reference\_counting">подсчет ссылок</a> ) ...</p><pre><code>{ std::shared\_ptr<t> p(new t(3.14, "pi")); number\_storage.may\_add(p); // might copy p into its container } // p's destructor will only delete the t if number\_storage didn't copy </code></pre><h2>нулевые указатели</h2><p>в c <code>null</code> и <code>0</code> - и дополнительно в c++ <code>nullptr</code> - могут использоваться для указания того, что указатель в настоящее время не содержит адрес памяти переменной и не должен разыменовываться или использоваться в арифметике указателей. например:</p><pre><code>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 </code></pre><p>в c и c++, так как встроенные числовые типы не обязательно по умолчанию равны <code>0</code> или <code>bools</code> - <code>false</code>, указатели не всегда устанавливаются в <code>null</code>. все они устанавливаются в 0/false/null, когда они являются <code>static</code> переменными или (только c++) прямыми или косвенными переменными-членами статических объектов или их баз, или подвергаются нулевой инициализации (например, <code>new t();</code> и <code>new t(x, y, z);</code> выполняют нулевую инициализацию для членов t, включая указатели, тогда как <code>new t;</code> нет).</p><p>кроме того, когда вы присваиваете <code>0</code>, <code>null</code> и <code>nullptr</code> указателю, биты в указателе не обязательно сбрасываются: указатель может не содержать "0" на аппаратном уровне или ссылаться на адрес 0 в вашем виртуальном адресном пространстве. компилятору разрешено хранить что-то еще там, если у него есть для этого причина, но что бы он ни делал - если вы приедете и сравните указатель с <code>0</code>, <code>null</code>, <code>nullptr</code> или другим указателем, которому был назначен любой из них, сравнение должно работать как положено , итак, ниже исходного кода на уровне компилятора "null" потенциально немного "волшебен" в языках c и c++ ...</p><h2>подробнее об адресах памяти и почему вам, вероятно, не нужно знать</h2><p>точнее, инициализированные указатели хранят битовый шаблон, идентифицирующий либо <code>null</code>, либо (часто <a href="http://en.wikipedia.org/wiki/virtual\_address\_space">виртуальный</a> ) адрес памяти.</p><p>в простом случае это числовое смещение во всем виртуальном адресном пространстве процесса; в более сложных случаях указатель может относиться к некоторой конкретной области памяти, которую цп может выбирать на основе регистров "сегмента" цп или некоторого вида идентификатора сегмента, закодированного в битовой комбинации, и/или просматривая в разных местах в зависимости от инструкции машинного кода с использованием адреса.</p><p>например, <code>int*</code>, должным образом инициализированный для указания на переменную <code>int</code>, может - после приведения к <code>float*</code> - получить доступ к значению в памяти "gpu", совершенно отличному от переменной <code>int</code>, а затем приведенный к указателю функции может ссылаться на отдельную память, содержащую машинные коды для функции.</p><p>языки программирования 3gl, такие как c и c++, как правило, скрывают эту сложность, так что:</p><ul> <li> <p>если компилятор дает вам указатель на переменную или функцию, вы можете разыменовать его свободно (если переменная не уничтожена/не удалена в это время), и это проблема компилятора, например, конкретный регистр процессора должен быть восстановлен заранее, или используется отдельная инструкция машинного кода</p> </li> <li> <p>если вы получаете указатель на элемент в массиве, вы можете использовать арифметику указателей для перемещения в другое место в массиве или даже для формирования адреса один за другим в конце массива, что допустимо для сравнения с другими указателями на элементы в массиве (или которые были аналогично перемещены арифметикой указателя к тому же значению "один за другим"); опять же в c и c++, это зависит от компилятора, чтобы убедиться, что это "просто работает"</p> </li> <li> <p>специальные функции ос, например, отображение общей памяти, может дать вам указатели, и они будут "просто работать" в пределах диапазона адресов, который имеет для них смысл</p> </li> <li> <p>попытки переместить законные указатели за эти границы, или привести произвольные числа к указателям, или использовать указатели, приведенные к несвязанным типам, обычно имеют <a href="http://en.wikipedia.org/wiki/undefined\_behavior"><em> неопределенное поведение </em></a> , поэтому следует избегать в библиотеках и приложениях более высокого уровня, но код для операционных систем, драйверов устройств и т. д., возможно, должен полагаться на поведение, оставленное неопределенным в c или c++, что, тем не менее, хорошо определяется их конкретным оборудованием.</p> </li> </ul>

<p>разыменование указателя означает получение значения, которое хранится в ячейке памяти, указанной указателем. оператор * используется для этого и называется оператором разыменования.</p><pre><code>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's location, we have 10. so, dereferencing gives this value. // since we have indirect control over a's location, we can modify its content using the pointer. this is an indirect way to access a. *ptr = 20; // now a's content is no longer 10, and has been modified to 20. </code></pre>

<p>указатель - это "ссылка" на значение ... так же, как номер вызова библиотеки - это ссылка на книгу. "разыменование" номера вызова физически проходит и извлекает эту книгу.</p><pre><code>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.. </code></pre><p>если книги нет, библиотекарь начинает кричать, закрывает библиотеку, и несколько человек собираются выяснить причину, по которой человек найдет книгу, которой там нет.</p>

<p>проще говоря, разыменование означает доступ к значению из определенной области памяти, на которую указывает этот указатель.</p>

<p>код и объяснение из <a href="http://cslibrary.stanford.edu/106/">основы указателя</a> :</p><blockquote> <p>операция разыменования начинается с указателя и следует за его стрелкой, чтобы получить доступ к указателю. цель может состоять в том, чтобы посмотреть на состояние pointee или изменить состояние pointee. операция разыменования для указателя работает, только если указатель имеет указатель - указатель должен быть выделен, и указатель должен быть установлен, чтобы указывать на него. самая распространенная ошибка в коде указателя - забывание установить pointee. самая распространенная ошибка во время выполнения из-за этой ошибки в коде - неудачная операция разыменования. в java некорректная разыменование будет вежливо помечено системой выполнения. в скомпилированных языках, таких как c, c++ и pascal, неправильное разыменование иногда приводит к сбою, а иногда к повреждению памяти каким-то тонким, случайным образом. по этой причине может быть трудно отследить ошибки указателя в скомпилированных языках.</p> </blockquote><pre><code>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 } </code></pre>

<p>я думаю, что все предыдущие ответы неверны, поскольку они утверждают, что разыменование означает доступ к фактическому значению. вместо этого википедия дает правильное определение: <a href="https://en.wikipedia.org/wiki/dereference\_operator">https://en.wikipedia.org/wiki/dereference\_operator</a> </p><blockquote> <p>он работает с переменной указателя и возвращает значение l, эквивалентное значению по адресу указателя. это называется "разыменование" указателя.</p> </blockquote><p>тем не менее, мы можем разыменовать указатель, не обращаясь к значению, на которое он указывает. например:</p><pre><code>char *p = null; *p; </code></pre><p>мы разыменовали указатель null без доступа к его значению. или мы могли бы сделать:</p><pre><code>p1 = &(*p); sz = sizeof(*p); </code></pre><p>опять же, разыменование, но никогда не доступ к значению. такой код не будет аварийно завершаться: сбой происходит, когда вы фактически <em> обращаетесь </em> к данным по недействительному указателю. однако, к сожалению, согласно стандарту разыменование недопустимого указателя является неопределенным поведением (за некоторыми исключениями), даже если вы не пытаетесь прикоснуться к фактическим данным.</p><p>короче говоря: разыменование указателя означает применение к нему оператора разыменования. этот оператор просто возвращает l-значение для вашего будущего использования.</p>