Будь ласка, додайте приклад із поясненням.
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 .
Наприклад, якщо випадковий літеральний рядок p
знаходився за адресою 0x1000 та 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 повинен бути призначений і покажчик повинен бути встановлений, щоб вказувати на нього. Найпоширенішою помилкою коду вказівника є забуття налаштування пункту. Найпоширеніший збій часу виконання через цю помилку в коді - це невдала операція скидання. У 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);
Знову перенаправлення, але ніколи не отримуючи доступу до цінності. Такий код НЕ буде збій: Збій відбувається, коли ви фактично отримуєте доступ до даних за допомогою недійсного вказівника. Однак, на жаль, згідно зі стандартом, перенаправлення недійсного покажчика є невизначеною поведінкою (за кількома винятками), навіть якщо ви не намагаєтесь торкнутися фактичних даних.
Отже, коротше: перенаправлення покажчика означає застосування оператора dereference до нього. Цей оператор просто повертає l-значення для подальшого використання.
*p;
викликає невизначеність поведінки. Незважаючи на те, що ви праві, що перенаправлення не має доступу до значення як таке , код *p;
дійсно має значення.