Added 04_merged.md + br to index
This commit is contained in:
		
							
								
								
									
										340
									
								
								pages/mipt_cxx1/04_merged.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								pages/mipt_cxx1/04_merged.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,340 @@
 | 
			
		||||
# 2.2 Kinds of memory
 | 
			
		||||
Условно в первом приближении при запуске программы ей дают непрервыный кусок памяти по адресам которых она может обращаться.
 | 
			
		||||
 | 
			
		||||
Условно эта память делится на три части - 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 *` значит, что мы вправе менять указатель, но не вправе менять то, что лежит под ним.
 | 
			
		||||
 | 
			
		||||
Запись `char const *` наоборот. А `const char const *` самая сильная из них.
 | 
			
		||||
 | 
			
		||||
[Здесь][rt-lt] подробно описано, как читать такие объявления.
 | 
			
		||||
 | 
			
		||||
**Null-terminated strings**. В языке Си есть конвенция отождествлять строку с последовательностью байт, которая заканчивается на `0`
 | 
			
		||||
(для записи NUL символа можно использовать литерал `'\0'`). 
 | 
			
		||||
 | 
			
		||||
Если нам дан указатель на начало, легко, например узнать длину строки, или скопировать одну в другую
 | 
			
		||||
 | 
			
		||||
```C
 | 
			
		||||
size_t
 | 
			
		||||
my_strlen(const char *s)
 | 
			
		||||
{ 
 | 
			
		||||
    // const char * означает, что мы вправе менять указатель, но
 | 
			
		||||
    // но не вправе менять то, что лежит под ним
 | 
			
		||||
    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"; // сам строковый литерал всегда хранится в статической памяти, сам указатель на него - на стеке
 | 
			
		||||
	std::cout << strlen(s) << ' ' << s << '\n'; // выведет '3 abc', потому что из-за поддержки C-style string все пытается прыгать вокруг присутствия /0 ровно в конце строки
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Под каждым `std::string` лежит C-style null-terminated строка, указатель на начало которой
 | 
			
		||||
получить вызвав метод `.c_str()`.
 | 
			
		||||
 | 
			
		||||
# 2.4 Functions and pointers to functions 
 | 
			
		||||
 | 
			
		||||
Указатель на функцию это "указатель на код", его также можно передавать в другие функции.
 | 
			
		||||
Объявляется он как `return-type (*name)(arguments)`.
 | 
			
		||||
 | 
			
		||||
Опять же, по [этой ссылке][rt-lt] можно найти гайд по чтению этих записей.
 | 
			
		||||
 | 
			
		||||
```C++
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
int sqr(int x) {
 | 
			
		||||
	return x * x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int f(int x);
 | 
			
		||||
 | 
			
		||||
void sort(int *begin, int *end, bool(*cmp)(int, int));
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
	int x = 0;
 | 
			
		||||
	std::cout << &x << '\n';
 | 
			
		||||
	int(*p)(int) &sqr; // синтаксически корректное определние ссылки на функцию
 | 
			
		||||
	
 | 
			
		||||
	std::cout << (void*)&sqr; // адрес sqr в памяти
 | 
			
		||||
 | 
			
		||||
	int (*q)(int) = &f; // нельзя, так как функция не определена
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Вызов функции по указателю медленнее, чем ее вызов напрямую, поэтому лучше
 | 
			
		||||
избегать их если можно(например, с помощью шаблонов).
 | 
			
		||||
 | 
			
		||||
```C++
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
int sqr(int x) {
 | 
			
		||||
	return x * x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double sqr(double x) {
 | 
			
		||||
	return x * x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
	int (*p)(int) = sqr;
 | 
			
		||||
	double (*pp)(double) = sqr;
 | 
			
		||||
 | 
			
		||||
	double (*ppp)(double) = (double(*)(double))(p);
 | 
			
		||||
 | 
			
		||||
	std::cout << (void*)p << ' ' << (void*)pp << '\n'; // выведет 2 разных адреса, потому что оно взяло разные функции 
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Существуют также variadic функции. Ниже адаптирован пример
 | 
			
		||||
[отсюда](https://www.gnu.org/software/libc/manual/html_node/Variadic-Example.html)
 | 
			
		||||
 | 
			
		||||
**TL;DR** Не пишите C-style variadic функции в C++. Будьте ОЧЕНЬ аккуратны при использовании variadic функций в C/C++.
 | 
			
		||||
 | 
			
		||||
```C
 | 
			
		||||
// C-style variadic function
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
variadic_sum(size_t count, ...)
 | 
			
		||||
{
 | 
			
		||||
    va_list ap;
 | 
			
		||||
    size_t i;
 | 
			
		||||
    int sum;
 | 
			
		||||
 | 
			
		||||
    va_start(ap, count);
 | 
			
		||||
 | 
			
		||||
    sum = 0;
 | 
			
		||||
    for (i = 0; i < count; ++i) {
 | 
			
		||||
        sum += va_arg(ap, int); // макрос принимает va_list и тип аргумента, который мы ждём
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    va_end(ap); // может не делать ничего, но по стандарту нужно
 | 
			
		||||
 | 
			
		||||
    return sum;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Данный код очень небезопасен по многим причинам. Первый аргумент `count` лежит на стеке, поэтому компилятор знает, как его достать. А
 | 
			
		||||
дальше могут лежать аргументы произвольных типов, которые могут занимать разное количество памяти.
 | 
			
		||||
 | 
			
		||||
Как тогда вытащить их со стека? `ap`
 | 
			
		||||
указывает на текущую позицию(сокращение от `argument pointer`), а `va_arg` приводит `ap` к типу, в нашем случае `int`, и прибавляет
 | 
			
		||||
количество байт, которое этот тип занимает.
 | 
			
		||||
 | 
			
		||||
Можно подумать, что в `va_start` мы передаем количество аргументов, но это неправда. `va_start` это **макрос**, а не функция, поэтому у
 | 
			
		||||
него есть немного контекста, например, адрес переменной `count`.
 | 
			
		||||
Он берет указатель на `count` и прибавляет к нему число байт, которое `count` занимает, чтобы указать на первый
 | 
			
		||||
variadic аргумент.
 | 
			
		||||
 | 
			
		||||
Тем самым, вторым аргументом в `va_start` нужно указать **последний не variadic аргумент функции**.
 | 
			
		||||
 | 
			
		||||
Одна из самых известных variadic функций с которой вы наверняка сталкивались, это `printf`.
 | 
			
		||||
Если передать в `printf` недостаточное число аргументов, слишком много аргументов, или тип аргумента
 | 
			
		||||
не совпадет со спецификатором(например, вы передали `char*` в `%d`), то **произойдет UB** как раз из
 | 
			
		||||
механизма работы `va`. Например, оно может брать аргументы выше по стеку, поэтому **никогда не передавайте
 | 
			
		||||
в `printf` недоверенный спецификатор формата, например, `prinf(username);` из соображений безопасности**.
 | 
			
		||||
@@ -9,7 +9,7 @@ _EOF
 | 
			
		||||
 | 
			
		||||
for page in *.md; do
 | 
			
		||||
    p=${page%.md}
 | 
			
		||||
    echo "<a href=\"$p.html\">$p</a>"
 | 
			
		||||
    echo "<a href=\"$p.html\">$p</a><br>"
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
cat <<-_EOF
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user