From 846363cd4c19df9f06e309be23abf14eaccb2c17 Mon Sep 17 00:00:00 2001 From: thematdev Date: Fri, 29 Sep 2023 00:35:47 +0300 Subject: [PATCH] Added 04_merged.md + br to index --- pages/mipt_cxx1/04_merged.md | 340 ++++++++++++++++++++++++++++++ pages/mipt_cxx1/generate_index.sh | 2 +- 2 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 pages/mipt_cxx1/04_merged.md diff --git a/pages/mipt_cxx1/04_merged.md b/pages/mipt_cxx1/04_merged.md new file mode 100644 index 0000000..f407f94 --- /dev/null +++ b/pages/mipt_cxx1/04_merged.md @@ -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 + +int f(int x) { + std::cout << '\n'; + ++x; + f(x); +} + +int main() { + f(0); +} +// переполнение стека +``` + +```C++ +#include + +int f() { + static int x = 0; + std::cout << '\n'; + ++x; + f(); +} + +int main() { + f(); +} +// переполнение стека, but cooler +``` + +```C++ +#include + +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 + +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 + +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 + +int main() { + int *p = new int[100]; + + delete[] p; +} +``` +Если пытаться сделать `delete[]` от обычного указателя или наоборот - это runtime error + +```C++ +#include + +int main() { + std::vector v(10); + v[-1] = 10000; // надругательство + return 0; + delete[] &v[0]; // надругательство + return 0; +} +``` + +```C++ +#include + +int main() { + // Variable lenght array (VLA) + int n = 100; + std::cin >> n; + int a[n]; // с cin нельзя, с n = 100 - можно +} +``` +Убрана обратная совместимость с C, чтобы не сдвигать переменные на стеке на какое-то неопредленное число, которое мы узнаем только в +рантайме. Но большинство компиляторов таки поддерживают VLA. +```C++ +#include + +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 +#include + +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 + +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 + +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);` из соображений безопасности**. diff --git a/pages/mipt_cxx1/generate_index.sh b/pages/mipt_cxx1/generate_index.sh index 927ba29..ee9afff 100755 --- a/pages/mipt_cxx1/generate_index.sh +++ b/pages/mipt_cxx1/generate_index.sh @@ -9,7 +9,7 @@ _EOF for page in *.md; do p=${page%.md} - echo "$p" + echo "$p
" done cat <<-_EOF