C++. Покажчики. Частина 2. Некеровані покажчики. Операції над покажчиками. Покажчик на тип void. Виділення пам’яті. Нульовий покажчик. Операція взяття адреси &

Покажчики. Частина 2. Некеровані покажчики. Операції над покажчиками. Покажчик на тип void. Виділення пам’яті. Ключові слова NULL та nullptr. Операція взяття адреси &

У даній темі описується робота з некерованими покажчиками. Як відомо, Visual C++ підтримує також і керовані покажчики.

Загальні відомості про покажчики, типи покажчиків детально описано тут.


Зміст


Пошук на інших ресурсах:

1. Які оператори можна використовувати над некерованими покажчиками?

Над некерованими покажчиками використовуються два оператори:

  • * – призначений для звертання до змінної, яка розміщується за адресою, що задається операндом цього оператора. Цей оператор посилається на значення змінної, яка записана в покажчику. Це є унарний оператор, в якому операнд розміщується справа.
  • & – повертає адресу пам’яті операнда, що розміщується справа. Це є унарний оператор.

Оператори * та & доповнюють один одного.

Наприклад. Якщо покажчик з іменем px посилається на змінну x дійсного типу, тобто маємо наступний опис:

float x;
float *px; // покажчик на змінну дійсного типу
px = &x;   // px вказує на x

то значення *px є значенням змінної x, що демонструє наступний рядок

*px = 25; // x = 25.0

А значення px є адресою змінної x в оперативній пам’яті.

C++ покажчик змінна рисунок

Рисунок 1. Покажчик px вказує на змінну x

2. Що таке операція непрямого доступу з допомогою покажчика?

Операція непрямого доступу – це доступ до змінної (об’єкту) з використанням покажчика. З допомогою покажчика можна отримувати доступ до масивів змінних (об’єктів).

Слово “непрямий” означає, що отримується доступ до значення змінної з допомогою іншої змінної.

3. Яким чином компілятор C++ визначає об’єм оброблюваної інформації, на яку вказує покажчик?

Об’єм оброблюваної інформації визначається базовим типом покажчика.

Приклад. Нехай дано такий опис:

double * pf;

базовий тип покажчика є double. Одна змінна типу double займає в пам’яті комп’ютера 8 байт. Тому компілятор C++ буде обробляти 8 байт інформації при доступі до неї за покажчиком.






4. Операція присвоювання покажчиків. Приклад

Значення одного покажчика можна присвоювати іншому при умові, що вони мають однаковий базовий тип (є покажчиками на однаковий тип даних).

Якщо покажчики вказують на різні типи даних, то працює операція приведення типів. У цьому випадку робота з покажчиком є непередбачуваною і може викликати невидимі логічні помилки (а може й ні).

Приклад.

// присвоювання покажчиків
int a = 5;      // звичайна змінна
int * pi1 = &a; // ініціалізація покажчика адресою змінної a
int * pi2;     // покажчик на int
double * pi3;   // покажчик на double

pi2 = pi1;     // допустимо

//pi3 = pi1; // помилка - "cannot convert from int* to double"

pi3 = (double *) pi1; // працює - операція приведення типів

5. Арифметичні операції над покажчиками. Зміна фізичної адреси покажчика. Приклади

Над покажчиками можна виконувати такі арифметичні операції:

  • операції інкременту ++ та декременту ;
  • операції додавання + та віднімання .

Покажчики можуть брати участь у виразах.

При зміні значення покажчика з допомогою арифметичних операцій фізична адреса покажчика змінюється за формулою:

N * sizeof(тип)

де

  • N – константа, що вказує зміну (збільшення/зменшення) значення покажчика;
  • тип – тип даних, на який вказує покажчик (базовий тип покажчика);
  • sizeof(тип) – операція, що визначає розмір типу даних.

Приклад 1. Зміна значення покажчика.

// збільшення/зменшення значення покажчика
int a = 5;     // звичайна змінна
int * pi1 = &a; // ініціалізація покажчика адресою змінної a
int * pi2;     // покажчик на int

pi2 = pi1;     // вказують на одну адресу пам'яті
pi2++;         // фізична адреса pi2 на 4 байти більша ніж адреса pi1

У вищенаведеному прикладі фізична адреса покажчика pi2 більша на 4 байти від фізичної адреси покажчика pi1. Це пов’язано з тим, що тип int (базовий тип покажчиків pi1 та pi2) має розмір 4 байти (середовище Win32).

Приклад 2. Зміна значення покажчика на тип double.

// збільшення/зменшення значення покажчика
double x = 3.55;   // звичайна змінна типу double
double * p1 = &x; // ініціалізація покажчика адресою змінної a
double * p2;     // покажчик на double

p2 = p1 - 4; // фізична адреса p2 на 32 байти менша ніж адреса p1
p2 = p1 + 2; // фізична адреса p2 на 16 байт більша ніж адреса p1

6. Які операції відношення (порівняння) можна виконувати над покажчиками?

Покажчики можна порівнювати. Для порівняння покажчиків використовуються операції ==, >, <.

При порівнянні покажчиків важливо, щоб ці покажчики мали деякий зв’язок між собою.

Наприклад, якщо покажчики вказують на різні нічим не зв’язані змінні, то операція порівняння не має сенсу.

У випадку, коли покажчики вказують на змінні, між якими є деякий зв’язок, то результат порівняння має зміст. Це може бути випадок, коли покажчики вказують на елементи одного й того ж масиву.

Приклад. Дано масив дійсних чисел A і 2 покажчики.

// порівняння покажчиків
float A[20]; // масив з 20 дійсних чисел
float *pa1, *pa2; // два покажчики на float
bool f_res; // результат порівняння покажчиків

// ...

pa1 = &A[5]; // вказує на 5-й елемент масиву
pa2 = &A[8]; // вказує на 8-й елемент масиву

f_res = pa1==pa2; // f_res = false
f_res = pa1>pa2;  // f_res = false
f_res = pa1<pa2;  // f_res = true

7. Які особливості використання покажчика на тип void? Приклад

Мова програмування C/C++ дозволяє використовувати покажчик на тип void.

Покажчик на тип void має свої особливості. Це означає, що покажчик на тип void є універсальним покажчиком, який може налаштовуватись на будь-який тип значень, в тому числі і нульовий. Ключове слово void дає компілятору інформацію, що відсутні дані про розмір об’єкту в пам’яті.

Щоб привести покажчик на void до покажчика на інший тип потрібно використовувати операцію приведення типів.

Приклад. Використання покажчика на тип void для доступу до змінних різних типів.

// покажчик на тип void
void * p;
int d;
float x;
char c;

d = 5;
x = 2.58f;
c = 'A';

p = (int *)&d; // p вказує на d
*(int *)p = 8; // d = 8

p = (float *)&x; // p вказує на x
*(float *)p = -8.75; // x = -8.75

p = (char *)&c; // p вказує на c
*(char *)p = 'r'; // c = 'r'

8. Яким чином покажчику присвоюється нульове значення?  Ключові слова NULL та nullptr. Приклад

Бувають випадки, коли покажчику потрібно присвоїти нульове значення. Це потрібно, щоб встановити значення покажчика таким, що не співпадає з жодним з покажчиків, що використовуються в програмі.

У модулі <stdio.h> визначена константа NULL з нульовим значенням. Її можна використовувати для присвоєння нульового значення покажчику.

Також для задавання нульового покажчика можна використовувати покажчик nullptr, який менш вразливий при неправильному використанні і в більшості випадків працює краще (дивіться приклад 2).

Приклад 1. Присвоєння нульового значення покажчику.

#include <stdio.h>

...

// нульовий покажчик
int * p;
p = nullptr; // працює
p = NULL; // працює, константа NULL описується в модулі <stdio.h>
p = 0;    // працює
p = 0x00; // також працює

Приклад 2. Випадок, коли краще використовувати покажчик nullptr ніж NULL.

#include <iostream>
using namespace std;

void func(std::pair<const char*, double> pr)
{
  // ...
}

void main()
{
  // Не працює - помилка компіляції
  //func(make_pair(NULL, 3.14)); // NULL => 0 => int

  // Працює
  func(make_pair(nullptr, 3.14)); // nullptr => nullptr_t => const char* 
}

У вищенаведеному прикладі покажчик NULL конвертується в число 0, яке за замовчуванням має тип int. А в нашому випадку потрібно щоб був тип const char*. При використанні покажчика nullptr цієї помилки не буде.

9. Приклад виділення пам’яті функцією malloc(), яка повертає покажчик на тип void.

Функція malloc() виділяє пам’ять для некерованого покажчика. Функція повертає тип void*.

Щоб присвоїти покажчику значення адреси пам’яті, виділеної для об’єкта, потрібно виконати явне приведення типу.

Приклад. Використання функції malloc().

// приклад виділення пам'яті під некерований покажчик
int * p;
p = NULL;

// виділення пам'яті функцією malloc() для покажчика на int
p = (int *)malloc(sizeof(int));

// виділення пам'яті функцією malloc() для покажчика на double
double * pd = NULL;
pd = (double *)malloc(sizeof(double));

10. Які обмеження накладаються на операцію взяття адреси &?

На операцію взяття адреси & накладаються наступні обмеження:

1. Неможливо визначити адресу константи. Наприклад вираз

pi = &0xffe800;

призведе до помилки.

2. Неможливо визначити адресу значення, яке отримується при обчисленні виразу. Наприклад фрагмент коду

int *pi;
int d;
d = 8;
pi = &(d + 5); // помилка, неможна взяти адресу виразу

призведе до помилки.

11. Чи можна керованому покажчику присвоювати безпосередньо фізичну адресу пам’яті?

Компілятор Visual C++ допускає можливість присвоєння покажчику безпосередньої адреси пам’яті. Однак, при доступі до цієї адреси виникає критична ситуація з повідомленням про помилку:

"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

Це означає, що відбулась спроба доступу до захищеної пам’яті.

Це стосується як консольних додатків, так і додатків створених за шаблоном Windows Forms Application.

Приклад. Присвоєння фізичної адреси покажчику.

// присвоєння фізичної адреси покажчику
float *pf; // покажчик на float
float f;

// ...

// присвоєння фізичної адреси (випадкове значення)
pf = (float *)0xffff00; // працює

f = (float)(*pf); // помилка! Виникає критична ситуація


Зв’язані теми