it-swarm.com.ru

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

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

460
asir

Обзор основной терминологии

обычно достаточно хорошо - если вы не программируете на Assembly - чтобы предусмотреть указатель содержащий числовой адрес памяти, где 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's 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's destructor's 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's destructor will only delete the T if number_storage didn't copy

Нулевые указатели

В C 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++, так как встроенные числовые типы не обязательно по умолчанию равны 0 или bools - false, указатели не всегда устанавливаются в NULL. Все они устанавливаются в 0/false/NULL, когда они являются static переменными или (только C++) прямыми или косвенными переменными-членами статических объектов или их баз, или подвергаются нулевой инициализации (например, 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++, что, тем не менее, хорошо определяется их конкретным оборудованием.

650
Tony Delroy

Разыменование указателя означает получение значения, которое хранится в ячейке памяти, указанной указателем. Оператор * используется для этого и называется оператором разыменования.

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.
85
Mahesh

Указатель - это "ссылка" на значение ... так же, как номер вызова библиотеки - это ссылка на книгу. "Разыменование" номера вызова физически проходит и извлекает эту книгу.

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.. 

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

16
bobobobo

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

10
Fahad Naeem

Код и объяснение из Основы указателя :

Операция разыменования начинается с указателя и следует за его стрелкой, чтобы получить доступ к указателю. Цель может состоять в том, чтобы посмотреть на состояние pointee или изменить состояние pointee. Операция разыменования для указателя работает, только если указатель имеет указатель - указатель должен быть выделен, и указатель должен быть установлен, чтобы указывать на него. Самая распространенная ошибка в коде указателя - забывание установить pointee. Самая распространенная ошибка во время выполнения из-за этой ошибки в коде - неудачная операция разыменования. В Java некорректная разыменование будет вежливо помечено системой выполнения. В скомпилированных языках, таких как C, 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   
}
7
atp

Я думаю, что все предыдущие ответы неверны, поскольку они утверждают, что разыменование означает доступ к фактическому значению. Вместо этого Википедия дает правильное определение: https://en.wikipedia.org/wiki/Dereference_operator

Он работает с переменной указателя и возвращает значение l, эквивалентное значению по адресу указателя. Это называется "разыменование" указателя.

Тем не менее, мы можем разыменовать указатель, не обращаясь к значению, на которое он указывает. Например:

char *p = NULL;
*p;

Мы разыменовали указатель NULL без доступа к его значению. Или мы могли бы сделать:

p1 = &(*p);
sz = sizeof(*p);

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

Короче говоря: разыменование указателя означает применение к нему оператора разыменования. Этот оператор просто возвращает l-значение для вашего будущего использования.

2
stsp