Условно в первом приближении при запуске программы ей дают непрервыный кусок памяти по адресам которых она может обращаться.
Условно эта память делится на три части - data, text и stack.
`data` - область памяти, где хранится все, что определено на всей области программы - то есть, в частности, глобальные переменные.
`text` - машинный код вашей программы.
`stack` - по дефолту примерно 8 MiB. Область ответственная за уровни вложенности программы, которая "как бы" хранит все в том порядке в котором оно определяется. (Хотя на самом деле компилятор вам не обязан ничего и может переопределять порядок или добавление, видоизменение, игнорирование как хочет)
При какой-либо рекурсии мы на стек добавляем не только все плокальные переменные функции, но и указатель на то место, где он был вызыван, иначе
мы не узнаем куда вернуться.
Сам стек примерно вмещает 1 миллион запусков пустой "рекурсии".
Динамическая память - память выделяемая в процессе выполнения программы
```C++
int *p = new int; // выделение памяти на int и получение его адреса
```
`new` -- создает объект данного типа в динамической памяти.
> new - это какое-то шаманство, которое пока разобрано не будет.
> Выполняется в 200-300 раз дольше чем сложение в хорошем случае.
> А в плохом сколь угодно плохо) ($\geq 10000$)
Утечка памяти(memory leak)
```C++
void foo() {
int* p = new int(5);
...
// without delete
return;
}
```
Если не вызвать `delete`, то память останется условно занятой, но провзаимодействовать мы с ней никак не сможем, так как потеряли адреса.
В отличие от многих других языков в C++ нет сборщика мусора, поэтому надо убирать за собой.
Дважды удалять один и тот же указатель - UB(double free or corruption).
Провзаимодействовать с указателем и вызвать `delete` - UB.
Вызвать `delete` не от указателя созданного `new` - UB.
После `delete p` разыменовать `p` - UB.
Статическая память
```C++
{
static int z;
}
```
Теперь `z` лежит в вышеупомянутой `data`, ей предвыделило чуть больше памяти на этапе компиляции, специально для `z` и оно будет храниться там на протяжении всей программы (но доступно только из своей области видимости).
**Важно**. Ключевое слово `static` имеет разный смысл для переменных внутри функций и глобальных переменных.
Для глобальных переменных `static` влияет на линковку(internal linkage, что бы это ни значило), противоположность
ключевому слову `extern`.
Ниже приведены примеры переполнения стека(из-за рекурсии)
```C++
#include <iostream>
int f(int x) {
std::cout << '\n';
++x;
f(x);
}
int main() {
f(0);
}
// переполнение стека
```
```C++
#include <iostream>
int f() {
static int x = 0;
std::cout << '\n';
++x;
f();
}
int main() {
f();
}
// переполнение стека, but cooler
```
```C++
#include <iostream>
int f() {
int *p = new int(5);
std::cout <<p<<''<<*p<<std::endl;
delete (++p);
f();
}
int main() {
f(0);
}
// переполнение стека
```
# 2.3 Arrays
```C++
#include <iostream>
int main() {
int a[10];
int b[5] = {1, 2, 3, 4, 5};
int c[5]{};
std::cout << *(a + 3) << '\n'; // 4
int *p = a + 3;
// p[i] == *(p + i), причем i[p] == p[i], потому что в терминах указателей это буквально i + p == p + i
std::cout <<p[-2]<<'\n';//2
}
```
`int*` и `int[]` взаимозаменяемы, поэтому следующий код вызовет ошибку компиляции
```C++
#include <iostream>
void f(int a[5]) {
;
}
void f(int* p) {
;
}
int main() {
int a[5] = {1, 2, 3, 4, 5};
int* b[5]; // массив из 5 указателей на int*, а не указатель на массив из 5 int
}
```
Массив можно выделить в динамической памяти (который на самом деле указатель на начало)
с помощью конструкции `new T[]`.
```C++
#include <iostream>
int main() {
int *p = new int[100];
delete[] p;
}
```
Если пытаться сделать `delete[]` от обычного указателя или наоборот - это runtime error
```C++
#include <iostream>
int main() {
std::vector<int> v(10);
v[-1] = 10000; // надругательство
return 0;
delete[] &v[0]; // надругательство
return 0;
}
```
```C++
#include <iostream>
int main() {
// Variable lenght array (VLA)
int n = 100;
std::cin >> n;
int a[n]; // с cin нельзя, с n = 100 - можно
}
```
Убрана обратная совместимость с C, чтобы не сдвигать переменные на стеке на какое-то неопредленное число, которое мы узнаем только в
рантайме. Но большинство компиляторов таки поддерживают VLA.
```C++
#include <iostream>
int main() {
const char* s = "abcdef"; // сам строковый литерал всегда хранится в статической памяти, сам указатель на него - на стеке
std::cout << (int)(s[4]) << '\n';
}
```
**Идентификатор `const`**.
Запись `const char *` значит, что мы вправе менять указатель, но не вправе менять то, что лежит под ним.
[Здесь][rt-lt] подробно описано, как читать такие объявления.
**Null-terminated strings**. В языке Си есть конвенция отождествлять строку с последовательностью байт, которая заканчивается на `0`
(для записи NUL символа можно использовать литерал `'\0'`).
Если нам дан указатель на начало, легко, например узнать длину строки, или скопировать одну в другую
```C
size_t
my_strlen(const char *s)
{
size_t len = 0;
while (*s++) {
len++;
}
// если гарантируется, что s заканчивается нулевым байтом, то
// мы никогда не выйдем за границы выделенной памяти, то есть данный код корректный
return len;
}
void
my_strcpy(char *dst, const char *src)
{
while ((*dst++ = *src++));
// Для того, чтобы не было UB, нужно, чтобы dst, src
// заканчивались нулем и в dst было достаточно места
}
```
```C++
#include <iostream>
#include <cstring>
int main() {
const char* s = "abc\0def"; // сам строковый литерал всегда хранится в статической памяти, сам указатель на него - на стеке