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


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

6 410

6 ответов:

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

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

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); выполнить ноль-инициализация по членам т, в том числе указатели, в то время как new T; тут не.)

далее, когда вы назначить 0,NULL и nullptr для указателя биты в указателе не обязательно все сбрасываются: указатель может не содержать "0" на аппаратном уровне или ссылаться на адрес 0 в вашем виртуальном адресном пространстве. Компилятору разрешено хранить там что - то еще, если у него есть причина, но что бы он ни делал-если вы придете и сравните указатель с 0,NULL,nullptr или другой указатель, который был назначен любому из них, сравнение должно работать так, как ожидалось. Таким образом, ниже исходного кода на уровне компилятора "NULL" потенциально немного "волшебный" в языках C и c++...

подробнее об адресах памяти, и почему вам, вероятно, не нужно знать

более строго, инициализированные указатели хранят битовый шаблон, идентифицирующий либо NULL или a (часто виртуальный) адрес памяти.

простой случай, когда это числовое смещение во весь процесс виртуальное адресное пространство; в более сложных случаях указатель может быть относительно некоторой конкретной области памяти, которую процессор может выбрать на основе регистров "сегмента" процессора или некоторого способа идентификатора сегмента, закодированного в битовом шаблоне, и/или искать в разных местах в зависимости от инструкций машинного кода, использующих адрес.

например,int* правильно инициализирован, чтобы указать на int переменная might-после приведения к a float* - доступ к значению в памяти " GPU " довольно отличается от int переменная, затем после приведения к указателю функции может ссылаться на отдельную память, содержащую машинные коды операций для функции.

3gl языки программирования, такие как C и C++, как правило, скрывают эту сложность, так что:

  • если компилятор дает вам указатель на переменную или функцию, вы можете разыменовать его свободно (пока переменная не разрушена/освобождена), и это проблема компилятора, например конкретный регистр ЦП должен быть восстановлен заранее, или используется отдельная инструкция машинного кода

  • если вы получаете указатель на элемент в массиве, вы можете использовать арифметику указателя для перемещения в любом месте массива или даже для формирования адреса одного конца массива, который является законным для сравнения с другими указателями на элементы в массиве (или которые аналогично были перемещены арифметикой указателя на одно и то же значение одного конца); опять же, в C и C++, это до компилятора, чтобы убедиться, что это "просто работает"

  • конкретные функции ОС, например отображение общей памяти, могут дать вам указатели, и они будут "просто работать" в диапазоне адресов, который имеет смысл для них

  • попытки переместить законные указатели за эти границы или привести произвольные числа к указателям или использовать указатели, приведенные к несвязанным типам, обычно имеют неопределено поведение, так и должно быть следует избегать в библиотеках и приложениях более высокого уровня, но код для ОС, драйверов устройств и т. д. возможно, потребуется полагаться на поведение, оставленное неопределенным C или C++, которое, тем не менее, хорошо определено их конкретным оборудованием.

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

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.

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

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

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

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

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

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

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

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

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

char *p = NULL;
*p;

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

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

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

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