В предыдущем видео мы с вами узнали, что локальные переменные функции хранятся в специальной области памяти, которая называется стек. Хорошо, теперь давайте рассмотрим другой пример. У нас есть функция main, в которой объявлены два вектора целых чисел, ints1 и ints 2, а также в этой же функции объявлены два потока ввода из строк, is1 и is2. В первом потоке хранятся числа 1, 2, 3, 4, во втором — 5 и 7. Кроме того, у нас есть функция fill, которая принимает, собственно, поток ввода и вектор целых чисел по ссылке. Делает она простую вещь. Она читает из потока ввода числа последовательно и помещает их в вектор. Собственно, этой функцией mail мы пользуемся достаточно просто. Мы в первый вектор помещаем числа, которые хранятся в первом потоке. А во второй вектор помещаем числа из второго потока. Таким образом, по итогам работы этой программы в векторе ins1 будут числа 1, 2, 3, 4, в векторе ins2 будут числа 5 и 7. Хорошо, теперь давайте зададим вопрос. А где в памяти хранятся эти числа, которые мы поместили в наши вектора? Дело здесь в том, что на этапе компиляции компилятор не знает, сколько чисел будет помещено в каждый из векторов. И поэтому он не может заранее зарезервировать на стеке память, необходимую для хранения этих чисел. Что мы могли бы предположить? Мы могли бы предположить, что эта память берётся ниже стекового фрейма функции fill. Что имеется ввиду? Давайте представим, что сейчас выполняется функция fill. Вы можете видеть состояние стека в момент её исполнения. Кстати, обратите внимание, мы говорили, что локальные переменные функции хранятся в стековом фрейме, так вот для функции fill у нас в стековом фрейме не только переменная x, которая у нас здесь объявлена, но и формальные параметры этой функции is и ints, они тоже являются локальными переменными. И для них также резервируется место на стеке. Итак, вот ситуация, в которой у нас выполняется функция fill. Мы предполагаем, что числа, которые она поместит в вектор, помещаются ниже её стекового фрейма, то есть после того, как функция fill завершится, мы ожидаем, что стек будет выглядеть таким образом. Вершина стека находится уже на функции main, а ниже фрейма функции fill у нас разместились четыре целых числа. Но это предположение, конечно же, неверное, потому что второй запуск функции fill просто перезатрёт те числа, которые поместил в стек первый запуск функции. То есть запускаем нашу функцию fill во второй раз, вы можете видеть, что её стековый фрейм снова перестал быть бледным. То есть сейчас работает функция fill. И когда она завершит свою работу, то элементы второго вектора по нашему предположению будут помещены под фрейм функции fill. Соответственно, они перезатрут элементы первого вектора. И, естественно, мы понимаем, что для хранения элементов векторов ints1, ints2 стек нам не подходит. Нам нужен какой-то другой источник памяти. И, собственно, тут важный момент: почему нам не подошел стек? Дело в том, что вот эти целые числа, они как бы создаются внутри функции fill, когда мы считываем их из входного потока. Но жить эти объекты, эти целые числа, должны дольше, чем работает функция fill. Вот если бы вектор был объявлен внутри функции fill, наполнялся бы в ней и разрушался после её завершения, то такое можно было бы реализовать на стеке. Для этого есть специальные средства, мы пока их не рассматривали, но можно бы было. Здесь же проблема в том, что эти объекты, создаваемые функцией fill, они должны жить дольше, чем живёт функция fill. Именно поэтому стек нам не подходит, потому что по мере работы программы вызываются разные функции и память на стеке постоянно перезатирается новыми вызовами других функций. Поэтому, ещё раз скажем, для хранения элементов вектора нам стек не подходит и нам нужен другой источник памяти. И этот источник называется куча или по-английски heap. Во время работы программы, во время исполнения программы она может в любой момент обращаться в кучу и выделять в ней блок памяти произвольного размера или же освобождать ранее выделенные. И именно в куче стандартный вектор, которым мы все с вами пользуемся, хранит свои элементы. И давайте посмотрим, как программа из нашего примера использует кучу для хранения элементов вектора. Итак, мы снова запускаем функцию fill и концентрируемся на том моменте, когда она работает, она считывает числа и пополняет вектора. После того, как она закончит свою работу, в куче будет выделен блок памяти, достаточный для хранения четырёх целых чисел. И в этом блоке будут хранится числа 1, 2, 3, 4. При этом вектор inst1, объявленный нашей функцией main, будет некоторым образом ссылаться на эту область памяти в куче, и таким образам он будет иметь доступ к этим элементам. Как он ссылается, мы с вами рассмотрим в ближайшее время. Соответственно, когда у нас отработает функция fill во второй раз, когда она наполнит второй вектор, то в куче будет выделен другой отдельный блок памяти, достаточный для хранения двух целых чисел. И там будут храниться элементы 5 и 7, а вектор ints2, он будет ссылаться на них на этот блок памяти. Обратите внимание на важное отличие. Когда мы выдвигали нашу гипотезу про хранение элементов в стеке, то там второй вызов функции перетирал первый. Здесь же каждый вызов создаёт отдельный блок памяти в куче и там хранит элементы вектора. Таким образом, у нас такие данные не перетираются, когда они этого делать не должны. Давайте подведём итоги. Мы с вами узнали, что в модели памяти языка C++ есть два источника памяти — это стек и куча. В стеке размещаются локальные переменные функции, а также служебная информация для возврата из функции. В куче же размещаются те объекты, которые должны жить дольше, чем создавшая их функции. Мы это увидели на примере функции fill, которая заполняла вектора.