Указатели и память

Правильное использование const в C и C++

В этом уроке вы будете объявлять данные только для чтения с помощью const и различать способы сочетания const с указателями.

Введение

Ключевое слово const позволяет функции пообещать, что она не изменит полученные данные. Если кто-то случайно изменит данные с пометкой const, компилятор сразу сообщит об ошибке. Код становится безопаснее, а читателю легко понять, какие функции могут менять данные.

Главное

  • Указатель должен указывать только на существующий объект нужного типа или быть равен null.
  • Каждую выделенную область памяти нужно освободить ровно один раз.
  • Явно обозначайте, какая функция владеет памятью (ownership), а какие данные только читаются (const).

Примеры кода на C и C++

C
Запустить →
main.c
#include <stdio.h>

int sum(const int *values, size_t count) {
    int total = 0;
    for (size_t i = 0; i < count; ++i) total += values[i];
    return total;
}

int main(void) {
    const int values[] = {2, 4, 6};
    printf("%d\n", sum(values, 3));
    return 0;
}
Результат выполнения
12
C++
Запустить →
main.cpp
#include <array>
#include <iostream>

int sum(const std::array<int, 3>& values) {
    int total = 0;
    for (int value : values) total += value;
    return total;
}

int main() {
    const std::array values{2, 4, 6};
    std::cout << sum(values) << '\n';
}
Результат выполнения
12

Сравнение C и C++

Параметр const int *values означает, что функция sum читает элементы массива, но не меняет их. Читайте объявление, начиная с имени переменной: указатель на константные данные (const int *) защищает значение, на которое указывает, а константный указатель (int * const) нельзя перенаправить на другой адрес. В C++ есть также константные функции-члены, которые обещают не менять состояние объекта.

C

Владение памятью и её освобождение полностью лежат на программисте.

C++

Контейнеры и RAII автоматически освобождают ресурсы, когда заканчивается время жизни объекта.

Упражнения для практики

Запустите обе версии, а затем измените код, чтобы увидеть разницу в безопасности, которую даёт каждый язык.

  • Проверьте работу с нулевыми указателями и при ошибках выделения памяти.
  • Запустите программу с AddressSanitizer и UndefinedBehaviorSanitizer, чтобы найти ошибки работы с памятью.
  • Явно обозначьте, какой указатель владеет памятью, а какой служит только для чтения.