Перейти к содержимому
На этой странице

Память, указатели и управление ресурсами

Все темы C++ Developer

Подтемы:

В чем разница между указателем и ссылкой?

Основные различия между указателем и ссылкой в C++:

  1. Синтаксис и использование:

    • Указатель: Переменная, которая хранит адрес другой переменной.

      int a = 10;
      int* ptr = &a;
    • Ссылка: Альтернативное имя для уже существующей переменной.

      int a = 10;
      int& ref = a;
  2. Инициализация:

    • Указатель: Может быть инициализирован позже или изменён для указания на другой адрес.

      int b = 20;
      ptr = &b;  // Теперь ptr указывает на b
    • Ссылка: Должна быть инициализирована сразу при создании и не может быть изменена для ссылок на другой объект.

      int c = 20;
      ref = c;  // Изменяет значение a на 20, но ref по-прежнему ссылается на a
  3. Нулевое значение:

    • Указатель: Может быть нулевым (nullptr), что означает, что он не указывает ни на какой объект.

      int* ptr = nullptr;
    • Ссылку нужно связать с подходящим объектом или функцией. Сам язык не отслеживает время жизни цели: после её уничтожения ссылка может стать висячей.

  4. Операции разыменования:

    • Указатель: Требует операцию разыменования (*) для доступа к значению, на которое указывает.

      int value = *ptr;
    • Ссылка: Не требует специальной операции для доступа к значению.

      int value = ref;

Указатель можно переназначить и проверить на nullptr. Ссылка удобна как обязательный параметр и не переназначается, но не гарантирует время жизни объекта. В обоих случаях нужно избегать висячих обращений.


Для чего нужен указатель на функцию? Как его объявить?

Указатель на функцию используется для хранения адреса функции и позволяет вызывать функции через этот указатель. Это полезно для реализации обратных вызовов (callbacks), таблиц виртуальных функций и гибких механизмов обработки событий.

Объявление указателя на функцию:

  1. Объявление:

    • Для функции с прототипом void foo(int):

      void (*funcPtr)(int);
  2. Инициализация:

    • Присвоение адреса функции указателю:

      funcPtr = foo;
  3. Использование:

    • Вызов функции через указатель:

      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 не владеет объектом и сам его не освобождает.

  1. std::unique_ptr: Эксклюзивное владение ресурсом. Не может быть скопирован, только перемещён.

  2. std::shared_ptr: Совместное владение ресурсом. Поддерживает подсчёт ссылок, освобождает память, когда последний shared_ptr выходит из области видимости.

  3. 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). Использование такой ссылки приведёт к неопределённому поведению, что может вызвать ошибки или сбои программы.


Где может храниться переменная?

В языке программирования C переменные могут храниться в различных областях памяти, каждая из которых имеет свои особенности по времени жизни и области видимости. Вот основные места хранения переменных:

  1. Стек (Stack): Локальные переменные внутри функций обычно хранятся на стеке. Они создаются при вызове функции и уничтожаются при её завершении.

  2. Куча (Heap): Динамически выделенная память (через malloc, calloc, realloc и другие функции) находится в куче. Переменные в куче сохраняются до тех пор, пока не будут освобождены с помощью free.

  3. Глобальная память (Global Area): Глобальные и статические переменные хранятся в глобальной области памяти. Они инициализируются при запуске программы и существуют до её завершения.

  4. Регистры (Registers): Компилятор может оптимизировать программу, помещая некоторые переменные непосредственно в регистры процессора для быстрого доступа.

  5. Литеральный пул (Literal Pool): Константы, такие как строковые литералы, обычно хранятся в отдельной области памяти, оптимизированной для неизменяемых данных.


Какая разница между calloc и malloc?

Функции calloc и malloc используются в языке программирования C для динамического выделения памяти, но они работают немного по-разному:

  1. malloc (Memory Allocation):

    • Принцип работы: Выделяет блок памяти заданного размера.

    • Инициализация: Память, выделенная с помощью malloc, не инициализируется. Это означает, что начальные значения в выделенной памяти не определены (могут быть мусорные значения).

    • Синтаксис: void* malloc(size_t size);

    • Пример: int* ptr = malloc(10 * sizeof(int)); выделит память для 10 целочисленных значений, но не инициализирует их.

  2. 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++, — это переменная, которая хранит адрес памяти другой переменной. Указатели используются для следующих целей:

  1. Доступ к данным: Указатель позволяет прямой доступ к данным, на которые он указывает, что может использоваться для изменения этих данных.

  2. Управление памятью: С помощью указателей можно динамически выделять и освобождать память во время выполнения программы.

  3. Работа с массивами и строками: Указатели обеспечивают эффективный способ обработки массивов и строк.

  4. Передача аргументов по ссылке: Указатели позволяют функциям изменять значения переданных переменных и работать с большими структурами данных без значительных затрат на копирование.

  5. Создание сложных структур данных: Указатели используются для создания связанных структур данных, таких как списки, деревья и графы.

Указатель определяется в C с использованием символа звездочки *, а доступ к значению, на которое он указывает, осуществляется через оператор разыменования *. Получение адреса переменной для сохранения в указателе выполняется с помощью оператора адреса &.


Какой размер имеет указатель и от чего он зависит?

Размер указателя в языках программирования, таких как C и C++, зависит от архитектуры системы, на которой выполняется программа. Обычно размер указателя равен:

  • 32 бита (4 байта) на 32-битных системах.

  • 64 бита (8 байт) на 64-битных системах.

Это связано с тем, что размер указателя должен быть достаточным для хранения максимально возможного адреса памяти, который может быть использован в данной архитектуре. Таким образом, размер указателя напрямую зависит от размера адресного пространства операционной системы и аппаратной архитектуры.


Какие есть операции с указателями?

Операции с указателями в языках программирования, таких как C и C++, позволяют манипулировать адресами и данными, на которые указывают эти адреса. Вот основные операции, которые можно выполнять с указателями:

  1. Присваивание: Установка адреса переменной в указатель.

    int a = 10;
    int* ptr = &a;  // Указатель ptr теперь указывает на a.
  2. Разыменование: Доступ к данным по адресу, хранящемуся в указателе.

    int value = *ptr;  // value теперь равно 10, значение a.
  3. Сдвиг указателя: Прибавление целого n смещает указатель на n элементов в пределах одного массива, включая позицию сразу после конца. Разыменовывать позицию после конца нельзя.

    ptr++;  // Перемещение на следующий адрес, типично для типа данных (например, на 4 байта вперед для int во многих системах).
  4. Сравнение: Сравнение адресов, хранящихся в указателях.

    if (ptr1 == ptr2)  // Проверка, указывают ли два указателя на одно и то же место в памяти.
  5. Арифметика указателей: Вычисление разницы между двумя указателями, что дает количество элементов между ними.

    int* ptr1 = &a[10];
    int* ptr2 = &a[5];
    int n = ptr1 - ptr2;  // n будет равно 5, если ptr1 и ptr2 указывают на элементы одного массива.
  6. Получение адреса: Использование оператора & для получения адреса переменной.

    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). В результате, используемая память остаётся занятой и недоступной для повторного использования, что может привести к исчерпанию доступной памяти и замедлению или краху программы.

Собеседования: C++

Смотри записи интервью, узнай, какие вопросы задают и как отвечают кандидаты.

Вопросы и ответы

Не нашли ответ? Напишите мне в чат. Я делаю Шпаргалку и сам отвечаю на сообщения. Расскажите, что не работает или чего вам не хватает. Может, смогу сразу взять это в работу.

Откуда взяты вопросы?

Из реальных собеседований. Основой подборки стал опыт Вадима Новосёлова: он проходил интервью и записывал вопросы. Подробнее о материалах.

Насколько эти вопросы актуальны?

Эти вопросы встречались нам на реальных собеседованиях в 2025 году. Мы регулярно проходим собеседования и пополняем подборку новыми вопросами. Основы профессии и ключевые технологии остаются востребованными годами, а детали конкретных инструментов и версий стоит сверять с текущей документацией.

На какой уровень рассчитана подборка?

Мы проходили собеседования на вакансии уровня Middle+, а иногда и на Senior-позиции. Вопросы из этих интервью вошли в подборку. Направления работы: C++-разработчик, Разработчик системного ПО. Глубина обсуждения зависит от вакансии: будь готов объяснить основную идею, привести практический пример и разобрать ограничения и альтернативы решения.

Этот вопрос точно будет на моём собеседовании?

Гарантии нет: набор вопросов зависит от компании, задач команды, уровня вакансии и самого интервьюера. Эти вопросы уже встречались на реальных собеседованиях, но на твоём интервью ту же тему могут проверить другой формулировкой, практической задачей или обсуждением твоего опыта. Используй подборку, чтобы разобраться в теме: объясняй идею своими словами, приводи примеры и готовься обсудить ограничения и альтернативы решения. Так будет проще ответить и на знакомый вопрос, и на неожиданные уточнения.