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

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

6 ответов


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

это обычно достаточно хорошо-если вы не программируете сборку-предусмотреть указатель содержит числовой адрес памяти, причем 1 относится ко второму байту в памяти процесса, 2-к третьему, 3-к четвертому и так далее....

  • что случилось с 0 и первым байтом? Ну, к этому мы еще вернемся-смотри!--55-->нулевые указатели под.
  • для более точного определения того, что хранят указатели, и как связаны память и адреса, см. "больше об адресах памяти, и почему вам, вероятно, не нужно знать".

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

различные компьютерные языки имейте разные обозначения, чтобы сообщить компилятору или интерпретатору, что вы теперь заинтересованы в указанном значении-я фокусируюсь ниже на 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

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

In 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, nor bools to 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 или a (часто виртуальный) адрес памяти.

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

например, an int* правильно инициализирован, чтобы указать на int переменная might-после приведения к 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.. 

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


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

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