Пожалуйста, включите пример с объяснением.
int *p;
определил бы указатель на целое число и *p
разыменовал бы этот указатель, означая, что он фактически получит данные, на которые указывает p.
Пожалуйста, включите пример с объяснением.
int *p;
определил бы указатель на целое число и *p
разыменовал бы этот указатель, означая, что он фактически получит данные, на которые указывает p.
Ответы:
Это , как правило , достаточно хорошо - если вы не программируя сборки - предусмотреть указатель , содержащий числовой адрес памяти, с 1 со ссылку на вторые байты в памяти процесса, 2 третьих, четвертые 3 и так далее ....
Когда вы хотите получить доступ к данным / значению в памяти, на которые указывает указатель - к содержанию адреса с этим числовым индексом - тогда вы разыменовываете указатель.
Различные компьютерные языки имеют разные нотации, чтобы сообщить компилятору или интерпретатору, что вы сейчас заинтересованы в (текущем) значении объекта, на который указывает указатель - ниже я остановлюсь на C и C ++.
Рассмотрим в C, учитывая указатель, такой как p
ниже ...
const char* p = "abc";
... четыре байта с числовыми значениями, используемыми для кодирования букв «a», «b», «c» и 0 байтов для обозначения конца текстовых данных, хранятся где-то в памяти, а числовой адрес этого данные хранятся в p
. Таким образом, C кодирует текст в памяти, известный как ASCIIZ .
Например, если строковый литерал оказался по адресу 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)
++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 = (int*)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...
free(p);
В 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
для владения акциями (с использованием подсчета ссылок ) ...
{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
В C NULL
и 0
- и дополнительно в C ++ nullptr
- может использоваться для указания того, что указатель в настоящее время не содержит адрес памяти переменной, и не должен разыменовываться или использоваться в арифметике указателей. Например:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
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
переменная, затем один раз приведен и использован как указатель на функцию, которая может указывать на дальнейшее отдельные машинные коды операций для хранения памяти (с числовым значением int*
фактически случайного, недействительного указателя в этих других областях памяти).
Языки программирования 3GL, такие как C и C ++, как правило, скрывают эту сложность, так что:
Если компилятор дает вам указатель на переменную или функцию, вы можете разыменовать его свободно (если переменная не уничтожена / не удалена в это время), и проблема компилятора заключается в том, нужно ли, например, предварительно восстанавливать конкретный регистр сегмента ЦП, или используется отдельная инструкция машинного кода
Если вы получаете указатель на элемент в массиве, вы можете использовать арифметику указателей для перемещения в другое место в массиве или даже для формирования адреса один за другим в конце массива, что допустимо для сравнения с другими указателями на элементы в массиве (или которые были аналогично перемещены арифметикой указателя к тому же значению «один за другим»); опять же в C и C ++, это зависит от компилятора, чтобы убедиться, что это "просто работает"
Определенные функции ОС, например отображение общей памяти, могут дать вам указатели, и они будут «просто работать» в пределах диапазона адресов, который имеет для них смысл
Попытки переместить законные указатели за эти границы, или привести произвольные числа к указателям, или использовать указатели, приведенные к несвязанным типам, обычно имеют неопределенное поведение , поэтому их следует избегать в библиотеках и приложениях более высокого уровня, но код для ОС, драйверы устройств и т. Д. Возможно, потребуется полагаться на поведение, оставленное неопределенным в стандарте C или C ++, что, тем не менее, хорошо определяется их конкретной реализацией или оборудованием.
p[1]
и *(p + 1)
одинаковые ? То есть ли p[1]
и *(p + 1)
генерировать те же инструкции?
p
равен всего 2000: если бы у вас был другой указатель на p
него, пришлось бы хранить 2000 в его четырех или восьми байтах. Надеюсь, это поможет! Приветствия.
u
содержит массив arr
, и gcc, и clang распознают, что lvalue u.arr[i]
может обращаться к тому же хранилищу, что и другие члены объединения, но не распознают, что lvalue *(u.arr+i)
может это делать. Я не уверен, считают ли авторы этих компиляторов, что последний вызывает UB, или что первый вызывает UB, но они все равно должны его обработать с пользой, но они явно рассматривают два выражения как разные.
Разыменование указателя означает получение значения, которое хранится в ячейке памяти, указанной указателем. Оператор * используется для этого и называется оператором разыменования.
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.
[]
также разыменовывает указатель ( a[b]
определяется как среднее *(a + b)
).
Указатель - это «ссылка» на значение ... так же, как номер вызова библиотеки - это ссылка на книгу. «Разыменование» номера вызова физически проходит и извлекает эту книгу.
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 или изменить состояние 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
}
Я думаю, что все предыдущие ответы неверны, поскольку они утверждают, что разыменование означает доступ к фактическому значению. Вместо этого Википедия дает правильное определение: https://en.wikipedia.org/wiki/Dereference_operator
Он работает с переменной указателя и возвращает l-значение, эквивалентное значению по адресу указателя. Это называется разыменованием указателя.
Тем не менее, мы можем разыменовать указатель, не обращаясь к значению, на которое он указывает. Например:
char *p = NULL;
*p;
Мы разыменовали указатель NULL без доступа к его значению. Или мы могли бы сделать:
p1 = &(*p);
sz = sizeof(*p);
Опять же, разыменование, но никогда не доступ к значению. Такой код НЕ вылетает: сбой происходит, когда вы фактически получаете доступ к данным по недействительному указателю. Однако, к сожалению, согласно стандарту, разыменование недействительного указателя является неопределенным поведением (за некоторыми исключениями), даже если вы не пытаетесь прикоснуться к фактическим данным.
Короче говоря: разыменование указателя означает применение к нему оператора разыменования. Этот оператор просто возвращает l-значение для вашего будущего использования.
*p;
вызывает неопределенное поведение. Хотя вы правы, что разыменование не имеет доступа к значению как таковому , код *p;
действительно получает доступ к значению.