Память, указатели и управление ресурсами
Подтемы:
В чем разница между указателем и ссылкой?
Основные различия между указателем и ссылкой в C++:
-
Синтаксис и использование:
-
Указатель: Переменная, которая хранит адрес другой переменной.
int a = 10; int* ptr = &a; -
Ссылка: Альтернативное имя для уже существующей переменной.
int a = 10; int& ref = a;
-
-
Инициализация:
-
Указатель: Может быть инициализирован позже или изменён для указания на другой адрес.
int b = 20; ptr = &b; // Теперь ptr указывает на b -
Ссылка: Должна быть инициализирована сразу при создании и не может быть изменена для ссылок на другой объект.
int c = 20; ref = c; // Изменяет значение a на 20, но ref по-прежнему ссылается на a
-
-
Нулевое значение:
-
Указатель: Может быть нулевым (
nullptr), что означает, что он не указывает ни на какой объект.int* ptr = nullptr; -
Ссылку нужно связать с подходящим объектом или функцией. Сам язык не отслеживает время жизни цели: после её уничтожения ссылка может стать висячей.
-
-
Операции разыменования:
-
Указатель: Требует операцию разыменования (
*) для доступа к значению, на которое указывает.int value = *ptr; -
Ссылка: Не требует специальной операции для доступа к значению.
int value = ref;
-
Указатель можно переназначить и проверить на nullptr. Ссылка удобна как обязательный параметр и не переназначается, но не гарантирует время жизни объекта. В обоих случаях нужно избегать висячих обращений.
Ссылки для изучения
Для чего нужен указатель на функцию? Как его объявить?
Указатель на функцию используется для хранения адреса функции и позволяет вызывать функции через этот указатель. Это полезно для реализации обратных вызовов (callbacks), таблиц виртуальных функций и гибких механизмов обработки событий.
Объявление указателя на функцию:
-
Объявление:
-
Для функции с прототипом
void foo(int):void (*funcPtr)(int);
-
-
Инициализация:
-
Присвоение адреса функции указателю:
funcPtr = foo;
-
-
Использование:
-
Вызов функции через указатель:
funcPtr(5); // Вызывает foo(5)
-
Пример:
#include <iostream>// Обычная функция
void foo(int x) {
std::cout << "Value: " << x << std::endl;
}
int main() {
// Объявление указателя на функцию
void (*funcPtr)(int);
// Инициализация указателя адресом функции foo
funcPtr = foo;
// Вызов функции через указатель
funcPtr(5);
return 0;
}
Ссылки для изучения
Что будет, если забыть вызвать delete? Когда освободится та память?
Если забыть вызвать delete для динамически выделенной памяти, произойдёт утечка памяти (memory leak). В таком случае:
-
Память, выделенная с помощью
new, не будет освобождена. -
Память будет занята до завершения программы.
-
Постепенно такие утечки могут привести к исчерпанию доступной памяти, что может вызвать замедление или крах программы.
Память, не освобождённая через delete, будет освобождена операционной системой при завершении программы, но в процессе выполнения программы эта память останется недоступной для повторного использования.
Ссылки для изучения
Что такое умный указатель? Какие умные указатели есть в стандартной библиотеке?
Умный указатель хранит указатель и реализует правила владения. unique_ptr освобождает ресурс при уничтожении или reset, shared_ptr при исчезновении последнего владельца. weak_ptr не владеет объектом и сам его не освобождает.
-
std::unique_ptr: Эксклюзивное владение ресурсом. Не может быть скопирован, только перемещён. -
std::shared_ptr: Совместное владение ресурсом. Поддерживает подсчёт ссылок, освобождает память, когда последнийshared_ptrвыходит из области видимости. -
std::weak_ptr: Неблокирующая ссылка на ресурс, управляемыйstd::shared_ptr. Не увеличивает подсчёт ссылок.
Пример:
#include <memory>
std::unique_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = std::make_shared<int>(20);
std::weak_ptr<int> ptr3 = ptr2;
Ссылки для изучения
Как работает const для переменных, ссылок и указателей и от чего зависит размер указателя?
Константность переменной, ссылки, указателя:-
Константная переменная: Значение не может быть изменено после инициализации.
const int a = 10; -
Константная ссылка: Ссылка на константу, через которую нельзя изменить значение.
const int& ref = a; -
Константный указатель: Указатель, адрес которого нельзя изменить после инициализации.
int value = 10; int* const ptr = &value; -
Указатель на константу: Указатель на значение, которое нельзя изменить через этот указатель.
const int* ptr = &a;
-
Константный указатель (
int* const): Указатель не может указывать на другой адрес, но данные по этому адресу могут изменяться.int value = 10; int* const ptr = &value; -
Указатель на константу (
const int*): Указатель может менять адрес, но данные по адресу менять нельзя.const int* ptr = &a;
- Размер указателя определяется типом указателя и реализацией; проверяйте sizeof. Следующие размеры типичны для распространённых ABI, но не гарантированы стандартом для всех платформ и указателей на члены.
-
32-битная система: Указатель занимает 4 байта.
-
64-битная система: Указатель занимает 8 байт.
-
Ссылки для изучения
Что случится, если вернуть ссылку на временный объект?
Если вернуть ссылку на временный объект, то после завершения функции объект будет уничтожен, и ссылка станет “висячей” (dangling reference). Использование такой ссылки приведёт к неопределённому поведению, что может вызвать ошибки или сбои программы.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Собеседование Middle C++ · 30:31–31:27Мок-собеседование · Объяснение интервьюера
Интервьюер объясняет, почему возвращённая ссылка становится висячей после уничтожения локального объекта, и почему результат обращения к ней нельзя предсказать.
Где может храниться переменная?
В языке программирования C переменные могут храниться в различных областях памяти, каждая из которых имеет свои особенности по времени жизни и области видимости. Вот основные места хранения переменных:
-
Стек
(Stack): Локальные переменные внутри функций обычно хранятся на стеке. Они создаются при вызове функции и уничтожаются при её завершении. -
Куча
(Heap): Динамически выделенная память (черезmalloc,calloc,reallocи другие функции) находится в куче. Переменные в куче сохраняются до тех пор, пока не будут освобождены с помощьюfree. -
Глобальная память
(Global Area): Глобальные и статические переменные хранятся в глобальной области памяти. Они инициализируются при запуске программы и существуют до её завершения. -
Регистры
(Registers): Компилятор может оптимизировать программу, помещая некоторые переменные непосредственно в регистры процессора для быстрого доступа. -
Литеральный пул
(Literal Pool): Константы, такие как строковые литералы, обычно хранятся в отдельной области памяти, оптимизированной для неизменяемых данных.
Ссылки для изучения
Какая разница между calloc и malloc?
Функции calloc и malloc используются в языке программирования C для динамического выделения памяти, но они работают немного по-разному:
-
malloc (Memory Allocation):
-
Принцип работы: Выделяет блок памяти заданного размера.
-
Инициализация: Память, выделенная с помощью
malloc, не инициализируется. Это означает, что начальные значения в выделенной памяти не определены (могут быть мусорные значения). -
Синтаксис:
void* malloc(size_t size); -
Пример:
int* ptr = malloc(10 * sizeof(int));выделит память для 10 целочисленных значений, но не инициализирует их.
-
-
calloc (Contiguous Allocation):
-
Принцип работы: Выделяет блок памяти для массива элементов заданного размера и количества.
-
Инициализация: Память, выделенная с помощью
calloc, автоматически инициализируется нулями. -
Синтаксис:
void* calloc(size_t num, size_t size); -
Пример:
int* ptr = calloc(10, sizeof(int));выделит память для 10 целочисленных значений и инициализирует их нулями.
-
Основное отличие между malloc и calloc заключается в инициализации: calloc инициализирует выделенную память нулями, в то время как malloc оставляет память неинициализированной.
Ссылки для изучения
Что такое указатель?
Указатель в программировании, особенно в языках C и C++, — это переменная, которая хранит адрес памяти другой переменной. Указатели используются для следующих целей:
-
Доступ к данным: Указатель позволяет прямой доступ к данным, на которые он указывает, что может использоваться для изменения этих данных.
-
Управление памятью: С помощью указателей можно динамически выделять и освобождать память во время выполнения программы.
-
Работа с массивами и строками: Указатели обеспечивают эффективный способ обработки массивов и строк.
-
Передача аргументов по ссылке: Указатели позволяют функциям изменять значения переданных переменных и работать с большими структурами данных без значительных затрат на копирование.
-
Создание сложных структур данных: Указатели используются для создания связанных структур данных, таких как списки, деревья и графы.
Указатель определяется в C с использованием символа звездочки *, а доступ к значению, на которое он указывает, осуществляется через оператор разыменования *. Получение адреса переменной для сохранения в указателе выполняется с помощью оператора адреса &.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Что можно знать о C++ в 15 лет? · 9:36–10:01Мок-собеседование · Совместный разбор
Участники уточняют определение указателя как адреса в памяти и отделяют его значение от места хранения самого указателя.
Какой размер имеет указатель и от чего он зависит?
Размер указателя в языках программирования, таких как C и C++, зависит от архитектуры системы, на которой выполняется программа. Обычно размер указателя равен:
-
32 бита (4 байта) на 32-битных системах.
-
64 бита (8 байт) на 64-битных системах.
Это связано с тем, что размер указателя должен быть достаточным для хранения максимально возможного адреса памяти, который может быть использован в данной архитектуре. Таким образом, размер указателя напрямую зависит от размера адресного пространства операционной системы и аппаратной архитектуры.
Ссылки для изучения
Какие есть операции с указателями?
Операции с указателями в языках программирования, таких как C и C++, позволяют манипулировать адресами и данными, на которые указывают эти адреса. Вот основные операции, которые можно выполнять с указателями:
-
Присваивание: Установка адреса переменной в указатель.
int a = 10; int* ptr = &a; // Указатель ptr теперь указывает на a. -
Разыменование: Доступ к данным по адресу, хранящемуся в указателе.
int value = *ptr; // value теперь равно 10, значение a. -
Сдвиг указателя: Прибавление целого n смещает указатель на n элементов в пределах одного массива, включая позицию сразу после конца. Разыменовывать позицию после конца нельзя.
ptr++; // Перемещение на следующий адрес, типично для типа данных (например, на 4 байта вперед для int во многих системах). -
Сравнение: Сравнение адресов, хранящихся в указателях.
if (ptr1 == ptr2) // Проверка, указывают ли два указателя на одно и то же место в памяти. -
Арифметика указателей: Вычисление разницы между двумя указателями, что дает количество элементов между ними.
int* ptr1 = &a[10]; int* ptr2 = &a[5]; int n = ptr1 - ptr2; // n будет равно 5, если ptr1 и ptr2 указывают на элементы одного массива. -
Получение адреса: Использование оператора
&для получения адреса переменной.int* ptr = &a; // Получение адреса переменной a.
Ссылки для изучения
Чем отличаются delete и delete[]? Что случится, если вызвать delete у объекта, созданного через new[]?
Отличия
delete
и
delete[]
:
-
delete: Используется для освобождения памяти, выделенной для одиночного объекта с помощьюnew.int* p = new int; delete p; -
delete[]: Используется для освобождения памяти, выделенной для массива объектов с помощьюnew[].int* arr = new int[10]; delete[] arr;
delete
вместо
delete[]
для объекта, созданного через
new[]
, приводит к неопределённому поведению. Это может вызвать:
-
Неполное освобождение памяти (утечка памяти).
-
Потенциальные ошибки выполнения или сбои программы.
Рекомендация:
Всегда использовать delete[] для освобождения памяти, выделенной с помощью new[], чтобы избежать неопределённого поведения и корректно освободить всю выделенную память.
Ссылки для изучения
Что такое memory leak?
Memory leak (утечка памяти) — это ситуация, когда программа
выделяет память динамически (например, с помощью new или
malloc), но не освобождает её (с помощью delete или
free). В результате, используемая память остаётся занятой и
недоступной для повторного использования, что может привести к исчерпанию
доступной памяти и замедлению или краху программы.
Ссылки для изучения







