Змінні

ред.

Змінна — це абстрактна назва комірки чи декількох комірок пам'яті. Кожна змінна має шість атрибутів: ім'я, адресу, значення, тип, область видимості і час життя.

У мові програмування C++ існують специфікатори збереження і кваліфікатори доступу. Із-за того, що вони не так часто використовуються, і існує деяка плутанина взаємозамінних термінів, іноді виглядає складною як для початківців, так і для тих хто вже програмує і використовує ці речі.

Кваліфікатори доступу

ред.

Кваліфікатори доступу ще іноді називають кваліфікаторами типів (Type Qualifiers), дозволяють задати змінним особливих властивостей, якщо їх вказати перед їх оголошенням. Існуть такі кваліфікатори:

  • const – змінні, оголошені з цим кваліфікатором, не можуть бути змінені в ході виконання програми.
  • volatile – модифікатор вказує компілятору, що значення змінної може бути змінене не явним способом, тобто без явного використання оператору присвоювання.
  • restrict – застосовується до оголошення або визначення функції, яка повертає вказівник; повідомляє компілятору, що цей об’єкт, який не можна буде зв’язувати ні з якими іншими вказівниками. З’явився лише в стандарті C99.

Специфікатори збереження

ред.

Специфікатори збереження (storage class), це такі спеціальні ключові слова, які використовуються для змінних і функцій і визначають область видимості і час існування змінної або функції в програмі C++. Ці специфікатори передують перед назвою типу. В мові C++ існують наступні специфікатори:

  • auto
  • register
  • static
  • extern
  • mutable

Автоматичні змінні

ред.

Специфікатор auto використовується для всіх локальних змінних за замовчуванням. Такі змінні оголошуються в межах блоку. Пам'ять виділяється автоматично в момент входження в область видимості даного блоку і вивільняється також автоматично, коли програма залишає даний блок. Область видимості таких змінних є локальною відносно того блоку, в якому вони оголошуються, включаючи всі блоки, які входять в даний блок. З цих обставин такі змінні частіше називають локальними змінними. Із жодного іншого блоку, який знаходиться за межами цього блоку доступитися на пряму до автоматичних змінних не можна, тобто по імені. Звичайно в такому випадку до них все ж таки можна доступитися за допомогою вказівників.

Автоматичні змінні можна визначити явно вказавши специфікатор auto. Але це не є обов'язковим. Автоматичні змінні, які оголошуються із ініціалізатором значення, будуть отримувати це початкове значення кожного разу, коли виконання програми входить в цей блок видимості.

{
   int mount;
   auto int month;
}

Статичні змінні

ред.

Специфікатор static для змінних вказує компілятору, що час існування локальної змінної має тривати доки триває виконання програми, замість того, щоб створювати її і видаляти з пам’яті кожен раз ми потрапляємо до блоку видимості, в якому оголошена змінна, і виходимо з нього. Таким чином оголошуючи локальну змінну як статичну ми отримуємо можливість зберігати її значення між різними викликами функції.

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

#include <iostream>
 
// Function declaration
void func(void);
 
static int count = 10; /* Глобальна змінна */
 
main()
{
    while(count--)
    {
       func();
    }
    return 0;
}

void func( void )
{
    static int i = 5; // локальна статична змінна
    i++;
    std::cout << "i = " << i ;
    std::cout << ", count = " << count << std::endl;
}

Після виконання цей код виведе на екран наступне:

i = 6, count = 9
i = 7, count = 8
i = 8, count = 7
i = 9, count = 6
i = 10, count = 5
i = 11, count = 4
i = 12, count = 3
i = 13, count = 2
i = 14, count = 1
i = 15, count = 0

Регістрові змінні

ред.

Ключове слово register застосовується до локальних змінних, якщо хочуть щоб вони зберігалися в регістрах процесора замість оперативної пам'яті RAM. Це означає, що змінна має максимальний розмір, який дозволено заносити в регістр, а також до неї не можна застосовувати унарний оператор взяття адреси комірки '&', оскільки ця змінна не має адреси в пам'яті.

{
   register int  miles;
}

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

В усіх інших випадках, регістрові змінні ведуть себе так само як автоматичні. Під них виділяється пам'ять в момент, коли програма входить в область видимості блоку і вивільняється, по виході з нього. Область видимості регістрових змінних обмежена блоком, в якій вона оголошена. Правила ініціалізації регістрових змінних такі самі як і у автоматичних змінних. Нарешті, навіть вказання змінній регістрового специфікатору не означає, що програма почне виконуватись швидше. Наприклад, якщо занадто багато змінних визначені як регістрові, або якщо в системі не має вдосталь доступних регістрів, щоб їх всі розмістити, значення декількох регістрів доведеться перемістити в тимчасове сховище в пам'яті. Таким чином, багато часу буде витрачатися на переміщення даних з регістрів у пам'ять і навпаки. Крім того, використання регістрів може відбуватися водночас з використанням їх компілятором, наприклад, для зберігання тимчасових значень при обчисленні виразів. Таким чином, використання регістрових змінних може призвести до зворотнього ефекту – сповільнення виконання програми. Їх слід використовувати лише в тому разі, коли ви досконало знаєте архітектуру комп'ютера і компілятор, які використовуються.

Зовнішні змінні

ред.

Зовнішні змінні визначаються за допомогою ключового слова extern, для того, щоб послатися на глобальну змінну, яка видима в усіх файлах програми. Зовнішня змінна не може бути ініціалізована початковим значенням, оскільки вона вказує на ім'я змінної, яка вже була визначена.

Зовнішні змінні можна оголосити поза межами будь-якого функціонального блоку файлу, у тому самому вигляді, як задаються будь-які інші змінні; задаючи її тип і ім'я. Ніякі специфікатори збереження не використовуються – місце визначення у файлі, у самому верхньому рівні за межами всіх блоків визначає те, що змінна є зовнішньою. Пам'ять для такої змінної буде виділена, в момент коли програма починає виконання, і звільнена коли програма завершиться. При використанні більшості компіляторів, кожен байт пам'яті, виділений для зовнішньої змінної буде ініціалізований нулями.

Якщо ви маєте декілька файлів і ви визначаєте глобальну змінну або функції, яка буде використовуватись в інших файлах також, тоді необхідно використовувати слово extern, щоб послатися на неї. В простому розумінні extern використовується щоб визначити глобальну змінну або функцію в іншому файлі.

Модифікатор extern зазвичай використовується якщо два або більше файлів використовують одні і ті самі глобальні змінні або функції, як у прикладі:

Перший файл: main.cpp

#include <iostream>
 
int count ;
extern void write_extern();
 
main()
{
   count = 5;
   write_extern();
}

Другий файл: support.cpp

#include <iostream>
 
extern int count;
 
void write_extern(void)
{
   std::cout << "Count is " << count << std::endl;
}

mutable змінні

ред.

Специфікатор mutable застосовується лише з об'єктами класів. Він дозволяє членам класу перевизначити константність. Mutable поля можна змінювати в константній функції класу.

Стекові і динамічні змінні

ред.

Розглядаючи стекові і динамічні змінні слід спочатку розглянути принципи організації пам'яті комп'ютера для програми, що виконується. Коли програма завантажується у пам'ять, вона організована у вигляді дерево-видної структури із областей пам'яті, які називаються сегментами: текстовий сегмент, стековий сегмент, і сегмент купи. Текстовий сегмент (який також іноді називається сегментом коду) це місце де знаходиться скомпільований код програми. Це машинне представлення програмних команд, які необхідно виконувати, включаючи всі функції з яких складається програма, написані програмістом і системні.

Дві області що залишилися це області де компілятор може виділити місце для збереження даних. Стек, це пам'ять виділена для зберігання автоматичних змінних, які використовуються в середині функції. Стек організований у вигляді черги «останній прийшов, перший пішов» (LIFO), в якій кожне нове сховище даних виділяється і звільняється лише в кінці, тобто у верху стеку.

Коли програма починає виконання функції main(), у стеку виділяється місце для всіх змінних визначених у всередині функції. Якщо в main(), викликається функція func1(), у стеку виділяється додаткове місце для збереження змінних функції func1(), яке буде знаходитись у верху стеку. Якщо функція func1() здійснить виклик ще якихось додаткових функцій, для них знову ж таки буде виділено місце у верху стеку. При виході із функції, сховище для її локальних змінних буде вивільнено (деалоковане), і вершиною стеку знов буде область пам'яті виділена для попередньої функції main(). Область стекової пам'яті, яка була звільнена, знов буде використана при необхідності, якщо функція main() зробить новий виклик додаткової функції. Очевидно, що область пам'яті, яка була використовувана перед цим і звільнена буде містити випадковий вміст (сміття), який залишається після попереднього використання.

Сегмент купи є більш постійним сховищем даних програми; пам'ять, яка виділена в цій області може існувати увесь час виконання програми. Тому, пам'ять під глобальні змінні (область збереження external), і статичні змінні виділяється із купи. Якщо пам'ять, виділена в цій області, буде ініціалізована нульовим значенням на початку виконання програми, вона збереже це значення до моменту, коли програма почне її використовувати. Тому, в цій області не повинне міститися сміття.