From 9f11ec59c495f7810427a5cc10826f9ce1f08569 Mon Sep 17 00:00:00 2001 From: thematdev Date: Thu, 21 Sep 2023 19:42:51 +0300 Subject: [PATCH] Add MIPT CXX lecture notes --- pages/mipt_cxx1/01_imported.md | 126 +++++++++++ pages/mipt_cxx1/02.md | 372 +++++++++++++++++++++++++++++++++ pages/mipt_cxx1/03.md | 228 ++++++++++++++++++++ pages/mipt_cxx1/Makefile | 22 ++ pages/mipt_cxx1/index.html | 0 5 files changed, 748 insertions(+) create mode 100644 pages/mipt_cxx1/01_imported.md create mode 100644 pages/mipt_cxx1/02.md create mode 100644 pages/mipt_cxx1/03.md create mode 100644 pages/mipt_cxx1/Makefile create mode 100644 pages/mipt_cxx1/index.html diff --git a/pages/mipt_cxx1/01_imported.md b/pages/mipt_cxx1/01_imported.md new file mode 100644 index 0000000..a533817 --- /dev/null +++ b/pages/mipt_cxx1/01_imported.md @@ -0,0 +1,126 @@ +# $I$. Introduction to C++ +## § 1.2 Types and their supported operations. + +static typization $:=$ тип переменной определяется на этапе компиляции + +Integral types: +```C++ +int x; //4 +long x; //4 +long long x; //8 +short x; //2 +char x; //? 1 +bool x; //?? 1 +int8_t x; +int16_t x; +int32_t x; +int64_t x; //стандартизованные типы про которые гарантируется их ширина +int128_t x; //есть аналоги с u +std::byte x; //аналогично char +size_t x; //==ull +``` +Floating point types: +```C++ +float x; //4b +double x; //8b +long double x; //16b +``` +Хранятся они в формате sign (1 bit) mantissa exponent, то есть число хранится как mantissa * 2^exponent * sign + +Все, что выше - это фундаментальные типы. Рассмотрим что-то более комплексное +```C++ +std::string s = "abc"; +s[1] == 'b'; +s[3] == (char)0; //string - это null termindated string - после данных стандарт гарантирует присутствие символа 0 +s[4]; //ub +.at() ++ += +.push_back() +.pop_back() +.front() +.back() +.size() +std::vector v; +//push_back работает амортизированно за O(1), но гарантируется за O(n) +//аналогично string без + и += +.shrink_to_fit() +.clear() +//просто делает из старых элементов ub, но память никак не освобождает +.reserve(n) +//предывделить память до n, но не кладет туда элементы (обращение туда все еще ub) +.resize(n, x) +//дропунть лишнее, если есть, если нет, то заполнить все остальное нулями +.capacity() +//size под выделенную память +std::list l; //useless +std::forward_list fl; //useless +std::deque d; //страшная вещь внутри, которую нужно будет реализовать +std::stack s; //deque внутри +std::queue q; //deque внутри +std::priority_queue pq; //deque с make_heap-ом внутри +std::map m; //rbt +m.at(key); //возвращает exception, если нет ключа +m.find(key); //возвращает итератор или m.end, если не находит +m.insert(pair); +erase(map::iter); + +std::multimap mm; //это multiset, который map......... +std::unordered_map um; //хэш-таблица +//работает В СРЕДНЕМ (не амортизированно) за O(1) +//то есть гарантируется, что работает за O(1) на рандомном наборе данных, а амортизированно - это значит, что работает быстро гарантированно +std::istream is; //поток ввода +std::ostream os; //поток вывода +``` +"Эффективность дороже безопасности". Любая проверка на ub отсутствует, потому что это дорого). + +Литералы +```C++ +5u; +1'000'000'057; +0.2f; +"abc"; +'a'; +//integer promotions - это когда ты складываешь int и long long, то получится long long и все будет корректно +//Есть аналогичное floating point promotion +//int + uint = uint +0xff == 255; //16-иричный +0123 == 83; //8-иричный +0b101 == 5; //2-ичный +``` + +## §1.1 Знакомство с терминалом +``` +pwd - print working directory +cd - вернуться в предыдущее пространство +``` + + +## §1.3 Declarations, definitions and scopes. +На глобальном уровне (вне какой-либо другой сущности) все действия - это объявления чего либо (класса, функции, переменной etc) + +```C++ +#include + +int a; + +void f(int x); + +class C; +struct S; +enum E; +union U; + +namespace N { + int x; +} + +namespace N { + int y; +} + +using ll = long long; + +int main() { + ; +} +``` diff --git a/pages/mipt_cxx1/02.md b/pages/mipt_cxx1/02.md new file mode 100644 index 0000000..3fa3c5c --- /dev/null +++ b/pages/mipt_cxx1/02.md @@ -0,0 +1,372 @@ +В прошлый раз мы остановились на пункте +Declarations, definitions and scopes + +Объявления +```C++ +int a; + +void f(int x); + +class C; +struct S; +enum E; +union U; + +namespace N { + int x; +} + +namespace N { + int y; +} +``` + +Чтобы сделать элемент видимым из другого namespace, +можно использовать using + +```C++ +int main() { + // std::cout << x << std::endl // CE + std::cout << N::x << std::endl // OK + using N::x; // теперь объявление x есть в нашей области видимости(scope) + std::cout << x << std::endl; // OK +} +``` + +Также using можно также добавить в глобальную область видимости и в другой namespace +Можно использовать сразу все пространство имен через `using namespace N`. + +Один из примеров использования: `using namespace std`, но это **крайне не рекомендуется**, +по крайней мере глобально. + +Причина тому то, что `std` крайне огромен, соответственно в нем очень много имен, +особенно опасно привнести функцию с одинаковым именем и другими типами, +например, `std::distance`. + +Еще один вид конструкции `using` это алиасинг типов, например, некоторые из смертных грехов +```C++ +using vi = std::vector; +using ll = long long; +``` + +Стоит понимать, что `using` не создает новый тип. `using` также можно использовать с шаблонами +```C++ +template +using v = std::vector; +``` + +Ключевые слова `typedef` и `using` семантически делают одно и то же, но `typedef` это C-style, +а значит, например, не умеет в шаблоны. + +Напомним, что _ключевыми словами_ называются слова, смысл которых закреплен компилятором, +и как следствие, их нельзя использовать, например, в качестве имён. + +Каждый блок(из фигурных скобок) создает новую область видимости, то есть +они образуют дерево по вложенности фигурных скобок(прим. лектора: +концептуально это так, но как всегда в C++, наверняка можно найти +несколько крайних случаев) + +По умолчанию определения берется из минимальной по включению области видимости, +но всегда можно обратиться к глобальной через `::`, например +```C++ +#include + +int x = 0; + +int main() { + int x = 1; + for (int i = 0; i < 10; ++i) { + int x = i; + // int x = 5; // CE, redeclaration in same scope + std::cout << x << std::endl; // i + std::cout << ::x << std::endl; // 0, if global doesn't exist will throw CE + } +} +``` + +Обратим внимание, что всегда можно создать блок и затемнить объекты +```C++ +int x = 5; + +{ + int x = 6; + // now x is 6 +} +``` + +Стоит обратить внимание на следующую "подлянку": +```C++ + +namespace N { + int x = 7; +} + +using namespace N; + +// WARNING: uncommenting this line will result to 0 as output +// By standard this is not redeclaration but using N::x is +// int x = 0; + +int main() { + int x = 1; + { + int x = 5; + std::cout << x << std::endl; // will print 7, as global scope includes x + } +} +``` + +Если `x` объявлен в двух различных пространствах `N, M`, подключенных +через `using namespace N; using namespace M;`, то при попытке использования `x` +компилятор выкинет ошибку `ambigious reference to variable x`. + +В первом приближении есть условно два типа, которые разрешаются в следующем приоритете: +1. Имена объявленные "явно", через `T x;` или `using N::x`. +2. Имена подключенные из другого пространства имен через `using namespace N;` + +Также можно получить ошибку `different kind of entity`, если определить +символ как тип и как переменную одновременно, например + +```C++ +using N::x; // int +using x = std::vector; // Type +``` + +Если начать заниматься еще большей херней, например `int x = x;`, то из-за +такой концепции как point of declaration, данный код эквивалентен `int x;`, +соответственно в `x` будет лежать мусор, а совсем не то, что будет в высшей +области видимости или пространстве имен. + +**One definition rule(ODR)**. Каждая сущность в программе должна быть определена ровно один раз. +Это верно в C, но не совсем верно для классов. Класс можно определить несколько раз, если +все определения абсолютно идентично. + +_Объявлений_ функций может быть сколько угодно, но _определение_ ровно одно. +Любое определение является объявлением. + +C++ разрешает более одной функции с одним и тем же именем. + +```C++ +void f(); +int f(int x) { + return x + 1; +} +int f(double x) { + return x + 2; +} +``` + +`f(0.5) = 2`, так как вызовется от `double`, `f(0) = 1`, так как вызовется от `int`. + +`f(0.0f) = 2`, ведь `float -> double` -- promotion, а `float -> int` -- conversion. +**promotion лучше, чем стандартный conversion, лучше чем тот, что определен пользователем**. + +Если бы определили `int f(float x);`, а вызывали бы `f(0.0) // no suffix defines double`, +то компилятор бы выкинул ошибку, так как он не может выбрать между двумя conversion. + +Следующий код также вызовет ошибку компиляции +``` +void f(); +int f(); +``` + +Это переобъявление(redeclaration) с одинаковым типом принимаемых значений, но разным возвращаемых. + +Необходимо различать (не)квалифицированные идентификаторы. +``` +x; // unqualified-id +N::x; // qualified-id +``` + +По стандарту `int x;` также считается определением, только если не стоит +ключевое слово `extern`. Выражение `extern int x;` является объявлением, +но не считается определением, линковщик будет искать этот символ. +`static` делает переменную видимой только в данном translation unit, +но также является определением(которое затемняет другие). + +# 1.4 Expressions and operators + +В локальных областях видимости появляются такие сущности, как +_выражения_(expressions) и _инструкции_(control flow statements), про +последние мы поговорим в следующем параграфе. + +Формальное определение выражения занимает одну-две страницы. +Неформально это просто набор некоторых переменных и литералов, +соединенных между собой операторами и скобками. + +**Пример.** `x + 5` или `cout << x` являются выражениями. + +Все мы знаем (бинарные) операторы `+, -, *, /, %, <<, >>, |, &, ^, &&, ||, <, >, !=, ==, <=, >=, <=>(since C++-20)` + +Побитовые операторы(`&, |, ^`) вычисляют выражение побитово. + +Логические операторы(`&&, ||`) принимают `bool` и возвращают `bool`, **и притом вычисляются лениво**. +Стандарт гарантирует, что если `a` ложно, то в выражении `a && b` выражение `b` вычислено не будет. +Аналогично, если `a` истинно, то в выражении `a || b` выражение `b` вычислено не будет. + +**Пример.** Выражение `v.size() >= 5 && v[4] == 1` безопасно и не вызывает UB из-за ленивости `&&`. + +Рассмотрим оператор присваивания `=` и операторы составного присваивания `+=, -=, *=, /=, %=, <<=, >>=, |=, ^=, &=`. + +`x = y` возвращает `y`. `(x = y) = z` возвращает `z`, все они lvalue. + +На интуитивном уровне, lvalue можно что-то присвоить, а rvalue нет, то есть `x + y` это rvalue, а `x = y` это lvalue. + +Первая группа операторов возвращает lvalue, а вторая rvalue. + +Операторы присваивания правоассоциативны, то есть `x = y = z` эквивалентно `x = (y = z)`. +Вроде как все операторы из первой группы левоассоциативны, то есть `x + y + z` эквивалентно `(x + y) + z`. + +Для `float` уже есть разница в каком порядке их складывать из-за точности(лучше в отсортированном). + +Лево(право)ассоциативность сохраняется в независимости от того, как оператор был перегружен. + +До C++-17 операторы `&&, ||` при перегрузке теряет ленивость и гарантию на порядок вычисления. +Начиная с C++-17 гарантируется, что при вычислении `a && b` выражение `b` будет +вычислено после выражения `a`. + +Ещё один класс операторов -- это декременты и инкременты, `a++, ++a, --a, --a`. + +Разница между `a++, ++a` заключается в том, что первый(постфиксный) создает новое +значение и кладет туда результат, тем самым возвращает rvalue. Второй(префиксный) увеличивает +переменную "на месте" и возвращает lvalue. Аналогично `a--, --a`. + +Инкременты и декременты применимы только к lvalue, то есть выражение `--(4 + 5)` некорректно, +а `++(x=5)` корректно. + +По стандарту постфиксные операции приоритетнее, и `++a++` эквивалентно `++(a++)`, а следовательно +некорректно. + +**Важный пример**. Чему эквивалентно выражение `a+++++b`? +**Лексический парсер(лексер) всегда пытается взять наибольший "осмысленный" токен**, +поэтому он разобъет его следующим образом: `((a++)++)+b`, но выражение `(a++)` является rvalue, +а инкремент можно применить только к lvalue. + +**Тернарный оператор**. `a ? b : c`. Пытается преобразовать `a` к `bool`, если истинно, то вычисляет `b` и +возвращает его, иначе вычисляет `c` и возвращает его. Гарантируется, что будет вычисляться ровно один из них. + +Вид возвращаемого value зависит от вида value `b` и `c`. Но вид value, как и тип, должен быть +известен на этапе компиляции, поэтому если хоть одно из них rvalue, то вид всего выражения +это rvalue, иначе lvalue. + +**Пример**. `(false ? a++ : ++a) = 1`, даже несмотря на то, что оно всегда равно `++a`, которое lvalue, +из рассуждения выше левая часть все равно будет rvalue, откуда присваивание некорректно. + +Если для `b` и `c` не найдется общий тип, но будет ошибка компиляции, иначе тип выражения равен общему типу. + +**Пример**. `f(b ? 1 : 0.5)` всегда вызовет `f(double)`. + +Оператор `,` делает следующее: выражение `a, b` вычисляет сначала `a`, потом `b`, +и возвращает `b`. Тип value такой же как у `b`. + +**Не всякая запятая как символ является оператором**. Запятая в `void f(int, int)` очевидно не +оператор, ведь это объявление, а не выражение, равно как и `<` в `#include `, которая +вообще является командой препроцессора. + +**Важное замечание**. `T x = y` это не оператор присваивания и не выражения, это синтаксис объявления. + +**Важный пример**. При вызове функции `f(x, y, z)` **нет никакой гарантии на порядок вычисления аргументов**, +`x, y, z` это не выражение, а перечисление аргументов. А если написать `f((x, y, z))`, то это будет +эквивалентно вызову функции `f` от одного аргумента `z`, выражения `x`, `y`, `z` будут вычислены +именно в таком порядке. + +Оператор `sizeof`. Возвращает количество байт, которое занимает переменная/типа. Так как тип +переменной `x` известен во время компиляции, то `sizeof(x)` также известен во время компиляции. + +`sizeof` от VLA(variable length array) является неопределенным поведением. VLA есть в +стандарте C99, но нет в стандарте C++, тем не менее, компиляторы его поддерживают. + +**Замечание**. `sizeof(v)` это не `v.size()`. `sizeof(arr)`, где `arr` -- +статически выделенный массив, это сумма размеров его элементов, +но от указателя он равен размеру самого указателя непосредственно. + +[Приоритеты операторов](https://en.cppreference.com/w/cpp/language/operator_precedence) + +[Порядок выполнения](https://en.cppreference.com/w/cpp/language/eval_order) + +Ниже приведены некоторые примеры: + +Нельзя сказать, чему равно значение `x++ * y++ + ++x`. Несмотря на то, что +умножение "выполняется" раньше, чем сложение, порядок вычисления операндов +не гарантируется. + +```C++ +int f() { std::cout << 1; return 1;} +int g() { std::cout << 2; return 2;} +int h() { std::cout << 3; return 3;} +``` + +В таком случае значение `f() * g() + h()` имеет однозначно определенное значения, +но вот результат его вычисления не определен. + +В выражении `a*b + c*d + e*f` нет никаких гарантий, что сначала вычислится результат всех умножений, +а только потом они будут складываться. + +Есть шедевральная глава "Sequenced before" rules, которое задает некоторые правила +порядка вычислений внутри одного потока, более точно, транзитивное антисимметричное бинарное отношение. + +Порядок выражений стал более строгим начиная с C++-17, тем самым некоторые выражения, вызывающие +неопределнное поведение до C++-17, хорошо определены после. + +Control statements +if: +```C++ +if (/* bool-expression */) { + +} else if (...) { + +} else { + +} +``` +switch: +```C++ +switch (/* bool-expression */) { + case 1: + std::cout << "AAAA"; + /* FALLTHROUGH */ + case 2: + std::cout << "BBBB"; + break; + case 3: + std::cout << "CCCC"; + default: + std::cout << "DEFAULT"; +} +``` +**Замечание**. Cases in switch **fall through**, например при `expression == 1` он выведет `AAAABBBB`, на `2` выведет `BBBB`, на +`3` выведет `CCCCDEFAULT`, иначе `DEFAULT`. +loops: +```C++ +// First check, then do +while (/* bool-expression */) { + +} + +// First do, then check +do { + +} while (/* bool-expression */); + +// first follows declaration, then check expression, and if true +// evaluates expression and inner block and repeats it +for (declaration | expression; bool-expression; expression) { + +} +// empty bool-expression is always true +``` + +Начиная с C++-17 можно делать объявление в ife: `if (int x = 0; y > x)`. + +Метки: +``` +label: + +// do some stuff + +if (bool-expression) { + goto label; +} +``` + +Нельзя прыгать с пропуском инициализации. diff --git a/pages/mipt_cxx1/03.md b/pages/mipt_cxx1/03.md new file mode 100644 index 0000000..5965090 --- /dev/null +++ b/pages/mipt_cxx1/03.md @@ -0,0 +1,228 @@ +# 1.6. Compile-time errors, runtime errors and undefined behaviour + +Мы уже встречались с ошибками компиляции. + +Ошибки можно условно разделить на лексические, синтаксические и +семантические. + +Мы уже затрагивали лексический парсер, он разбивает программу на +токены, например `std::cin >> x;` разбивается на 6 токенов +`std :: cin >> x ;`. + +Далее происходит синтаксический разбор, и компилятор +начинает заниматься семантикой. + +**Пример**. + +* `\\;` будет лексической ошибкой, потому что лексер не справится разобрать символ `\` с `error: stray '\' in program` + +* `6abcde;` будет семантической ошибкой, потому что компилятор подумает, что `abcde` -- литеральный суффикс, а потом его не найдет(начиная +с C++-11 литеральные суффиксы можно определять свои) + +* `std::cout << x + ;` будет синтаксической ошибкой, `expected primary-expression before ';' token`, так как нет выражения после плюса. + +* `"abc" + 5.0f;` будет семантической ошибкой, `invalid operands of types ...`. + +_Ошибка времени выполнения_ или runtime error это когда +программа успешно скомпилирована, но непредвиденно завершается во время +выполнения(падает). + +Одна из самых частых -- segmentation fault. + +```C++ +#include +#include + +int main() { + std::vector v(10); + v[50'000] = 1; +} +``` + +Возникает из-за обращения к памяти, которую мы не имеем права читать. + +Floating point exception (FPE), например, деление на ноль, идёт от процессора. + +Aborted, вызов функции `abort()` из libc, её вызов приводит к аварийному завершению. + +**Неопреденное поведение** или undefined behaviour -- некорректный код, который +приводит к "непредсказуемым" последствиям во время выполнения, в том смысле, +что компилятор не может дать никаких гарантий. + +```C++ +std::vector v(10); +v[10] = 1; // UB +// Может упасть, а может и повредить очень важную область памяти +// у другой переменной + +int x; +std::cout << x; // UB + +x++ + ++x; // UB, как говорилось раньше, нет гарантий на порядок +``` + +Signed integer overflow это также UB. В том смысле, что **компилятор вправе +считать, что в коде нет переполнений знаковых чисел и делать оптимизации +исходя из этого**. + +```C++ +for (int i = 0; i < 300; ++i) { + std::cout << i << ' ' << i * 12345678 << std::endl; +} +``` + +при компиляции с `g++ -O2` получается вечный цикл. Но почему? Ведь при проверке не возникает переполнение + +Компилятор считает, что выражение `i * 12345678` не переполняется, а значит `i < 174`, а значит **проверку можно убрать**. + +Также компилятор может убрать `assert(a + 100 > a)`, так как он вправе считать, что `a + 100 > a` всегда. + +Бесконечный цикл без побочных эффектов тоже является UB, компилятор имеет право сделать с ним что угодно, +см [C Compilers Disprove Fermat’s Last Theorem](https://blog.regehr.org/archives/140). + +Помимо неопределенного поведения есть ещё **unspecified behaviour**. Это значит, что +стандарт не говорит, что конкретно должно быть, например, порядок вычислений. + +То есть `f(a(), b(), c())` это не undefined behaviour, но unspecified, потому +что неясен порядок вычислений. + +**Implementation-defined behaviour** это то, что зависит от реализации и окружения, например, +от компилятора, локали, архитектуры, etc. + +За счёт undefined behaviour, компилятор может делать агрессивные оптимизации, +что ускоряет "корректный" код, зато с "некорректным" компилятор может +сделать что угодно. + +**Лирическое отступление**. + +**Warning** -- замечание компилятора относительно вашего кода, не обязательно нарушение стандарта. + +**Примеры**: + +```C++ +if (x = 0) { + // do something +} +``` + +Это формально корректный код, и даже используемая идиома, но `clang` кидает предупреждение, и +просит обернуть в скобки, если это сделано специально. + +```C + +pid_t pid; + +if ((pid = fork()) == -1) { + // error while forking, should handle +} +``` + +Ещё один пример, это unused value. Компилятор предупреждает, не забыли ли вы случайно использовать +значение, например `f();` если `f` возвращает не `void`. + +**Некоторые флаги** + +Флаг `-Wall` позволяет предупреждать "обо всём", `-Wextra` о том, о чём не предупреждает `-Wall`. + +Флаг `-pedantic` говорит компилятору строго чтить стандарт и не использовать свои расширения, по типу +VLA в C++. + +Флаг `-Werror` превращает все предупреждения в ошибки. + +Подробнее см. `man gcc`, `info gcc`(боже упаси) или [онлайн-документацию](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html). + +# II. Compound types + +## 2.1. Pointers + +Пусть есть какая-то переменная `T x`. У нее есть какой-то адрес, `&x`. Это не буквально её адрес на плашке +оперативной памяти, но по этому числу программа может обращаться к памяти. Для этого не нужно +вдаваться в подробности того, как работает операционная система. + +```C++ +int main() { + int x; + + std::cout << &x << std::endl; // may be different at different runs +``` + +А какой тип у `&x`? Это `T*`. Ясно, что должна существовать обратная операция к взятию адреса, _разыменование_. +`*p` по указателю `T*` возвращает то, что лежит под ним. + +```C++ +int x = 0; +int* p = &x; +*p = 2; +``` + +**Важно**. Несмотря на то, что `*` это часть типа, при объявлении нескольких указателей +её надо каждый раз проставлять заново + +```C++ +int *a, *b, c; +// a, b are pointers, c is integer +``` + +Самое главное, **к указателям можно прибавлять числа**. Этим и отличаются типы `T*, U*`, если `T` и `U` разных размеров. + +Указатель `p + n` ссылается "на `n` элементов типа `T` дальше, чем `p`". То есть к адресу прибавляется `n * sizeof(T)`. + +```C++ +std::vector v = {1, 2, 3, 4, 5}; // std::vector guarantees that they're aligned in a row +int *p = &v[0]; +std::cout << *(p + 3) << std::endl; // 4 +std::cout << *++p << std::endl; //2 +``` + +Указатели можно вычитать(конечно, если они одного типа), +например `&v[3] - &v[0]` равно `3`, а `&v[0] - &v[4]` равно `-4`. + +Но если вычесть два указателя, которые лежат не в одном куске, то получится +абсолютно случайное значение. + +Грубо говоря, разность это `(address(p) - address(q)) / sizeof(T)`. + +Ничего не мешает брать указатели на указатели. + +На 64-битной архитектуре размер указателя это 8 байт. Размер указателя не зависит +от типа, на который он указывает. + +Унарная `*` это lvalue(объект), а `&` это rvalue(его адрес), и его аргумент +должен быть lvalue(чтобы у него был адрес). + +```C++ +int a = 1; +int *p = &a; +{ + int b = 2; + p = &b; +} // lifetime of b ends here, so p points to trash +std::cout << *p << std::endl; // UB, but most likely 2 +``` + +Память может также переиспользоваться, если потом создать `int c = 3;`, например. + +Если вы работаете с указателями разных типов, то скорее всего вы делаете что-то не то. +Их нельзя даже сравнить, будет ошибка компиляции. + +Есть особый тип -- `void*`, указатель на непонятно что, любой указатель можно привести к нему, +но обратно без явного приведения нельзя. Но у него нет операций прибавления, разности, разыменовывания. + +В C++ есть ключевое слово `nullptr`(константа `NULL` в Си, равная нулю), указатель в никуда. +При его разыменовывании происходит неопреденное поведение. + +## 2.2 Kinds of memory + +data, text, stack + +data -- область памяти, в которой лежат глобальные переменные +text -- область памяти, куда загружена сама программа +stack -- область памяти, где хранятся локальные переменные + +Она называется stack, потому что она работает как стек, когда программа входит в функцию, +на стек кладутся аргументы, далее другие локальные переменные. После выхода из функции/области видимости +они извлекаются из стека, и.т.д. В рамках одной локальной области компилятор может класть переменные на +стек в произвольном порядке и делать промежутки. + +При входе в функцию также кладется "адрес возврата", откуда надо продолжить выполнять код, после того как +мы выйдем из функции. diff --git a/pages/mipt_cxx1/Makefile b/pages/mipt_cxx1/Makefile new file mode 100644 index 0000000..646cbbd --- /dev/null +++ b/pages/mipt_cxx1/Makefile @@ -0,0 +1,22 @@ +BUILDDIR ?= build + +PAGE := $(shell basename $(shell pwd)) + +PREFIX := $(BUILDDIR)/page/$(PAGE) + +MARKDOWN_FILES := $(wildcard *.md) +MARKDOWN_TARGETS = $(patsubst %.md,$(PREFIX)/%.html,$(MARKDOWN_FILES)) + +all: $(MARKDOWN_TARGETS) index + +$(PREFIX)/%.html: %.md + @mkdir -p $(@D) + pandoc $< --to html --output $@ --standalone + +$(PREFIX)/index.html: index.html + @mkdir -p $(@D) + cp $< $@ + +index: $(PREFIX)/index.html + +.PHONY: all index diff --git a/pages/mipt_cxx1/index.html b/pages/mipt_cxx1/index.html new file mode 100644 index 0000000..e69de29