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

402

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

  • 0
    это может вам помочь: stackoverflow.com/questions/2795575/…
  • 22
    cslibrary.stanford.edu/106
Показать ещё 5 комментариев
Теги:
pointers
dereference

6 ответов

544
Лучший ответ

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

Обычно он достаточно хорош - если вы не программируете сборку - чтобы предусмотреть указатель, содержащий числовой адрес памяти, причем 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++, которое, тем не менее, хорошо определено их конкретным оборудованием.

  • 0
    p[1] и *(p + 1) идентичны ? То есть генерирует ли p[1] и *(p + 1) одинаковые инструкции?
  • 2
    @Pacerier: из 6.5.2.1/2 в черновом стандарте C N1570 (впервые я нашел в сети) "Определение оператора нижнего индекса [] состоит в том, что E1 [E2] идентичен (* ((E1) + (E2)) ) «. - Я не могу представить причину, по которой компилятор не мог бы сразу преобразовать их в идентичные представления на ранней стадии компиляции, применяя те же оптимизации после этого, но я не вижу, как кто-то может однозначно доказать, что код будет идентичен без обзора каждого компилятора, когда-либо написанного.
Показать ещё 18 комментариев
84

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

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.
  • 13
    Указатель не указывает на значение , он указывает на объект .
  • 38
    @KeithThompson Указатель не указывает на объект, он указывает на адрес памяти, где находится объект (возможно, примитив).
Показать ещё 10 комментариев
14

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

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

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

9

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

7

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

Операция разыменования начинается с указатель и следует за его стрелкой над для доступа к его указателю. Цель может быть посмотреть состояние плацдарма или измените состояние плацдарма. операция разыменования указателя работает только в том случае, если указатель имеет 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   
}
  • 0
    Вы действительно должны выделить память для того места, на которое должен указывать x. Ваш пример имеет неопределенное поведение.
  • 1
    @Peyman Это будет команда malloc.
3

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

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

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

char *p = NULL;
*p;

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

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

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

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

  • 0
    ну, вы разыменовали NULL-указатель, что приведет к ошибке сегментации.
  • 0
    Кроме того, вы искали «оператор разыменования», а не «разыменование указателя», что фактически означает получение значения / доступ к значению в ячейке памяти, на которую указывает указатель.
Показать ещё 6 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню