C++ 1MIT весна 1 2020 — различия между версиями

Материал из CSC Wiki
Перейти к:навигация, поиск
(Лекции)
(Практика 1: Соколов)
Строка 62: Строка 62:
  
 
== Практика 1: Соколов ==
 
== Практика 1: Соколов ==
 +
Преподаватель: Вячеслав Соколов ([mailto:vi.soksok@gmail.com vi.soksok@gmail.com], +7 921 780 11 12)
 +
 +
Trac: <code>sokolov</code>
 +
 +
'''deadline первой попытки первой лабораторной: среда 09.11 21:00'''
 +
 +
Письма просьба идентифицировать префиксом в теме <code>[HSE][CPP]</code>
 +
 +
==== Linux ====
 +
* Минимальный набор для работы из консоли (для Ubuntu):
 +
** <code>man</code> manual
 +
** <code>ls</code> list
 +
** <code>pwd</code> print working directory
 +
** <code>mkdir</code> make (create) directory
 +
** <code>cd</code> change directory
 +
** <code>cp</code> copy
 +
** <code>rm</code> remove
 +
** <code>touch</code> "потрогать" объект на файловой системе
 +
** <code>mv</code> move
 +
** <code>gcc</code> gnu C compiler
 +
** <code>make</code> make
 +
** <code>svn</code> subversion
 +
** <code>apt</code> aptitude
 +
 +
* Текстовые редакторы
 +
** <code>subl</code> [https://www.sublimetext.com Sublime text]
 +
** <code>code</code> [https://code.visualstudio.com Visual Studio Code]
 +
 +
* Исследование бинарных артефактов
 +
** <code>nm</code> list symbols from object files
 +
** <code>readelf</code> displays information about ELF files
 +
** <code>c++filt</code> demangle symbols
 +
 +
===== Ограничение процесса по ресурсам в Linux =====
 +
* '''Рекомендуется''' использовать systemd-run: <code>systemd-run --scope -p MemoryLimit=2M -p MemoryAccounting=yes ./a.out</code>. В этом примере ограничивается память 2 мебибайтами.
 +
 +
Дальнеший текст - для тех, кто хочет более глубокого погружения.
 +
* [https://habr.com/ru/post/266083/ Подробная статья с разбором разных аспектов]. Понимать ее полностью не нужно, разбор достаточно подробный и из-за этого может быть непонятен.
 +
 +
Краткий перечень ключевых слов и какие есть проблемы при использовании:
 +
* cgroups - можно просто взять и использовать, но придется повозиться, не очень удобно. Требует понимания, как что устроено, соответственно и времени потребует.
 +
* ulimit - много разных версий, от конкретного shell-а зависит как пример использования, так и доступная функциональность. Легко может не сработать.
 +
* Использование LD_PRELOAD и собственного аллокатора памяти. У меня аллокатор libmemrestrict.so по ссылке из статьи крашится с SEGFAULT, возможно, это я где-то напортачил. Если захотите идти этим путем - спрашивайте меня, есть несколько нетрививальных моментов.
 +
* docker - популярный способ работы с контейнерами
 +
 +
==== Для тех, кому хочется поэкспериментировать с консолью ====
 +
 +
* Эмуляторы терминала
 +
** [https://gnometerminator.blogspot.com/p/introduction.html terminator]
 +
** [https://gnunn1.github.io/tilix-web/ tilix]
 +
** а также много других, легко ищутся
 +
 +
* Shell
 +
** <code>bash</code> по умолчанию, но есть кое-что поудобнее, например
 +
** [https://fishshell.com fish] - interactive shell, создан, чтобы быть удобным
 +
** [https://eax.me/zsh/ zsh] - для любителей писать скрипты
 +
 +
==== Системы сборки ====
 +
 +
Для наших нужд достаточно
 +
[https://habr.com/ru/post/155201/ Make]
 +
 +
Если проект становится большим, нужно что-то посложнее, например (альтернативы):
 +
 +
*[https://neerc.ifmo.ru/wiki/index.php?title=CMake_Tutorial CMake]
 +
*[https://ninja-build.org/manual.html ninja]
 +
*[https://bazel.build bazel]
 +
 +
==== Полезные сайты ====
 +
* [https://en.cppreference.com/w/c cppreference] - консультация со стандартом языка Си
 +
* [https://godbolt.org godbolt] - compiler explorer (удобно делиться примерами)
 +
* [http://ideone.com ideone] - возможность делиться кодом, поддержка разных ЯП, исполнение кода online
 +
* [https://stackoverflow.com stackoverflow] - огромное количество ответов на разные вопросы. Прежде чем самому задавать вопрос, будет полезно ознакомиться с текстом https://habr.com/ru/post/339038/
 +
* [https://cdecl.org cdecl] - поможет ответить на вопрос, что есть <code>int (*(*foo)(void ))[3]</code>
 +
* [https://github.com/nothings/single_file_libs] - список библиотек из 1-2 файлов
 +
* [https://en.cppreference.com/w/c/links/libs] - список open-source библиотек на языке Си
 +
* [https://en.cppreference.com/w/cpp/links/libs] - список open-source библиотек на языке Си++
 +
* [https://en.cppreference.com/w/cpp/language/history] - история языка Си++
 +
 +
==== Статьи ====
 +
* [https://en.cppreference.com/w/c/language/behavior UB в языке C]
 +
* [https://en.cppreference.com/w/cpp/language/ub UB в языке C++]
 +
* [http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r0.html integer overflow proposal] - предложение в Стандарт языка C++: integer overflow - не UB.
 +
* [http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1407r0.pdf another integer overflow proposal] - ну хотя бы Unspecified Behaviour, лишь бы не UB! (наболело)
 +
* [http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1705r0.html попытка решить проблему UB в языке C++]
 +
* [https://stackoverflow.com/questions/16979791/c-data-structures-alignment обсуждение про выравнивание]
 +
* [http://www.kroah.com/log/linux/container_of.html the weird Linux kernel container_of macro]
 +
* [https://ru.wikipedia.org/wiki/%D0%90%D0%BD%D1%82%D0%B8%D0%BF%D0%B0%D1%82%D1%82%D0%B5%D1%80%D0%BD Антипаттерны]
 +
* [https://habr.com/ru/company/pvs-studio/blog/348098/ Почему важно проверять, что вернула функция malloc]
 +
 +
==== Практика ====
 +
16.01.20
 +
* https://godbolt.org/z/Qgdzxv
 +
* https://godbolt.org/z/qWPHdR
 +
* https://godbolt.org/z/rXic4J
 +
 +
 +
==== Требования корректности, предъявляемые к работам ====
 +
'''1. Проверка контрактов функций.'''
 +
 +
На мой взгляд, чуть ли не единственной обязательной к использованию парадигмой и доступной во всех языках программирования, выше ассемблера, является [https://ru.wikipedia.org/wiki/Контрактное_программирование]. Ключевые понятия: предусловия, постусловия и инварианты. В языках C/C++ соблюдение контрактов контролируется с помощью assert, возведения сигналов, бросания исключений.
 +
 +
Данный подход является обязательным, потому что гарантирует максимально раннее обнаружение проблемы. Игнорирование подхода приводит к проблемам в самых неожиданных местах. Программа может вести себя как корректная, хотя содержит в себе большое количество ошибок, пока очередная проблема не приведет к лавине. Примеры неожиданно всплывших проблем: https://software-testing.ru/library/testing/general-testing/2082-horrible-bugs
 +
 +
Требуется производить проверку контрактов:
 +
* предусловий: с помощью assert в начале функции. Пример: Указатель: может быть нулевым? Нет? assert.
 +
* инвариантов: по ходу выполнения функции. Пример: по ходу алгоритма нужно произвести удаление элемента из контейнера. Можно проверить, что этот элемент вообще присутствует в контейнере.
 +
* постусловий: В конце функции и с помощью тестов. Пример: в результате удаления вершины из списка она не должна быть достижима из головы списка. Это можно провалидировать с помощью assert.
 +
 +
Проверки контрактов упрощают восприятие кода и могут ''при определенном стечении обстоятельств'' заменять документацию.
 +
 +
Q: но ведь проверка контракта может быть медленной, теряем производительность
 +
 +
A: используйте assert для медленных проверок, если нужна производительность - используется release режим сборки, туда эти проверки не попадут
 +
 +
Q: а что делать с malloc, который вернет NULL, это же и в release может случиться
 +
 +
A: сейчас у нас в арсенале нет способа просто решить эту проблему. Можно, но сложно. Поэтому ставим assert, чтобы развивать привычку. В заданиях незачем аллоцировать Очень Много Памяти, поэтому проблема не слишком актуальная. "В жизни" будет использоваться не assert, а какой-то другой механизм.
 +
 +
'''2. Стиль кода совпадает во всем проекте.'''
 +
 +
Либо везде табуляции для отступов, либо везде пробелы. Именование переменных либо везде snake_case, либо везде camelCase. Желательно отделять разные сущности друг от друга написанием. Например, можно выделять:
 +
* МАКРОСЫ()
 +
* КОНСТАНТЫ
 +
* функцииКоторыеЧтоТоДелают()
 +
* переменныеКоторыеЗачемТоПонадобились
 +
* ИменаКлассовИСтруктур
 +
Допустимы конфликты написания (когда по имени нельзя однозначно восстановить, какая это языковая конструкция), но не стоит для всего использовать одно и то же написание.
 +
 +
'''3. Каждая строчка кода что-то делает.'''
 +
 +
Если строчку кода можно удалить без изменения поведения программы, значит, ее нужно удалить.
 +
 +
'''4. В языке Си использовать <code>void(void)</code>, а не <code>void()</code>.'''
 +
 +
'''5. Отсутствие утечек памяти, Undefined Behaviour и других проблем'''
 +
Ваша программа должна компилироваться и корректно завершаться, а соответствующая утилита не должна находить какие-либо проблемы:
 +
* в debug и release (-DNDEBUG) режимах сборки
 +
* будучи запущенной из-под gdb
 +
* будучи запущенной из-под valgrind
 +
* после компиляции с -fsanitize=address
 +
* после компиляции с -fsanitize=undefined
 +
* после компиляции с -fsanitize=leak
 +
* на разных версиях разных компиляторов (gcc, clang, msvc, ...)
 +
включая комбинации этих опций, кроме случаев ошибок в самих утилитах и несоответствия компилятора Стандарту. (Да, и то, и то бывает.)
 +
 +
'''6. Именование должно быть понятным кому угодно, не только лишь Автору, но и всем.'''
 +
Имя функции должно отражать особенности ее поведения.
 +
Имя переменной должно быть говорящим.
 +
Лучше всего, если вне контекста можно понять, за что отвечает / что делает та или иная функция, структура, переменная. Стандарт языка в этом смысле не является примером для подражания.
 +
 +
Этот раздел не может быть формализовать и ложится целиком и полностью на здравый смысл. Здесь зачастую нет "идеального" решения, и если вам не удается достичь совершенства - не расстраивайтесь, таких как вы - легион. Проблема именования является одной из ключевых в программировании, вызывает большое количество дискуссий, требует много времени и внимания разработчиков.
 +
 +
'''7. Если была допущена какая-то проблема в реализации функции, должен быть написан тест, детектирующий эту проблему.'''
 +
 +
'''8. Использование сторонних функций только из стандартной библиотеки языка, если противное явно не оговорено в задании.'''
 +
 +
В частности, написание C/C++ кода без привязки к платформе Linux; отсутствие POSIX-специфичных функций и структур. Не смотря на то, что целевая платформа - Linux, привычка писать платформенно-специфичный или компиляторо-специфичный код может однажды сыграть злую шутку. Комитет по стандартизации стремится к тому, чтобы не было необходимости писать такой код, включая новые и новые платформенно-независимые вещи в стандарт языка. Если "совсем никак", то лучше обернуть специфичную функцию: сделать <code>size_t mygetline(char **lineptr, size_t *n, FILE *stream) {return getline(lineptr, n, stream);}</code>
 +
 +
'''9. Не использовать exit где-либо, кроме main.cpp'''
 +
Представьте, что вы пользуетесь какой-то библиотекой. Позвали функцию из нее, а ей что-то не понравилось (не хватило памяти, запись на диск не удалась, ...) и она позвала в своих недрах exit. Это полная катастрофа, потому что на этом работа программы завершается, а в месте вызова вы об этом даже не узнаете. Это может быть очень болезненно: может быть, в оперативной памяти хранится результат длительных вычислений, или ценная информация, это могли быть данные, за которые нужно платить (обращения к платным сервисам) и много что еще. Главное - за вас кто-то другой решил, как программа должна себя вести. Никогда не используйте exit в библиотеках.
 +
Во всех заданиях прослеживается структура: сделать некоторый набор действий в рамках некоторой модели. Модель находится в одном месте, управляющая логика - в другом. Здесь проходит условная черта: модель - это библиотека, управляющая логика - конечный пользовательский код.
 +
Обработка ошибок должна осуществляться с помощью кодов возврата в языке Си и с помощью кодов возврата либо исключений в языке Си++.
 +
В языке Си разрешается делать exit в том случае, если гарантированно освобождены все ресурсы (позваны free, fclose, ...). Например, разрешается в main обработать ошибки так: передать код возврата в специальную функцию, которая по коду возврата напечатает сообщение и позовет exit. Но делать так не рекомендуется, потому что эквивалентным образом можно позвать return из функции main: 
 +
 +
<code>
 +
void exitOnError(int ec) {...; exit(ec);}
 +
int main(){...; if (ec) {exitOnError(ec);}
 +
 +
vs
 +
 +
int printError(int ec){...; return ec;}
 +
int main(){...; if(ec) {return printError(ec);}
 +
</code>
 +
 +
 +
To be continued.
 +
 
== Практика 2: Свиридкин ==
 
== Практика 2: Свиридкин ==
 
== Практика 3: Лапшин ==
 
== Практика 3: Лапшин ==
 
== Практика 4: Гулецкий ==
 
== Практика 4: Гулецкий ==

Версия 20:36, 15 января 2020

Осенний семестр

Лекции

Лектор: Суворов Егор Федорович (egor_suvorov@mail.ru)

Весенний семестр

Планы лекций Егора: github.com/yeputons/hse-2019-cpp

Прочие материалы

Ссылки:

Осенний семестр

Презентации первого семестра Евгения Линского (I поток):

Планы лекций Егора (II поток): github.com/yeputons/hse-2019-cpp

Презентации ликбеза по С:

Книги

  • С нуля
    • С. Дэвис, C++ для чайников
    • Г. Шилдт, С++ базовый курс
  • Язык С
    • Б. Керниган, Д. Ритчи, Язык программирования C
    • Б. Керниган, Р. Пайк, Практика программирования
  • Язык C++
    • Б. Страуструп, Язык программирования С++
    • Б. Эккель, Философия C++
  • Дополнительно
    • Б. Страуструп, Дизайн и эволюция языка C++
    • С. Майерс, Эффективное использование С++/Эффективное использование STL
    • Г. Сеттер, Решение сложных задач на C++/Новые сложные задачи на C++
    • Р. Седжвик, Алгоритмы на C++
  • На английском

Оценка за курс

Лабораторные и домашние

Практика 1: Соколов

Преподаватель: Вячеслав Соколов (vi.soksok@gmail.com, +7 921 780 11 12)

Trac: sokolov

deadline первой попытки первой лабораторной: среда 09.11 21:00

Письма просьба идентифицировать префиксом в теме [HSE][CPP]

Linux

  • Минимальный набор для работы из консоли (для Ubuntu):
    • man manual
    • ls list
    • pwd print working directory
    • mkdir make (create) directory
    • cd change directory
    • cp copy
    • rm remove
    • touch "потрогать" объект на файловой системе
    • mv move
    • gcc gnu C compiler
    • make make
    • svn subversion
    • apt aptitude
  • Исследование бинарных артефактов
    • nm list symbols from object files
    • readelf displays information about ELF files
    • c++filt demangle symbols
Ограничение процесса по ресурсам в Linux
  • Рекомендуется использовать systemd-run: systemd-run --scope -p MemoryLimit=2M -p MemoryAccounting=yes ./a.out. В этом примере ограничивается память 2 мебибайтами.

Дальнеший текст - для тех, кто хочет более глубокого погружения.

Краткий перечень ключевых слов и какие есть проблемы при использовании:

  • cgroups - можно просто взять и использовать, но придется повозиться, не очень удобно. Требует понимания, как что устроено, соответственно и времени потребует.
  • ulimit - много разных версий, от конкретного shell-а зависит как пример использования, так и доступная функциональность. Легко может не сработать.
  • Использование LD_PRELOAD и собственного аллокатора памяти. У меня аллокатор libmemrestrict.so по ссылке из статьи крашится с SEGFAULT, возможно, это я где-то напортачил. Если захотите идти этим путем - спрашивайте меня, есть несколько нетрививальных моментов.
  • docker - популярный способ работы с контейнерами

Для тех, кому хочется поэкспериментировать с консолью

  • Эмуляторы терминала
    • terminator
    • tilix
    • а также много других, легко ищутся
  • Shell
    • bash по умолчанию, но есть кое-что поудобнее, например
    • fish - interactive shell, создан, чтобы быть удобным
    • zsh - для любителей писать скрипты

Системы сборки

Для наших нужд достаточно Make

Если проект становится большим, нужно что-то посложнее, например (альтернативы):

Полезные сайты

  • cppreference - консультация со стандартом языка Си
  • godbolt - compiler explorer (удобно делиться примерами)
  • ideone - возможность делиться кодом, поддержка разных ЯП, исполнение кода online
  • stackoverflow - огромное количество ответов на разные вопросы. Прежде чем самому задавать вопрос, будет полезно ознакомиться с текстом https://habr.com/ru/post/339038/
  • cdecl - поможет ответить на вопрос, что есть int (*(*foo)(void ))[3]
  • [1] - список библиотек из 1-2 файлов
  • [2] - список open-source библиотек на языке Си
  • [3] - список open-source библиотек на языке Си++
  • [4] - история языка Си++

Статьи

Практика

16.01.20


Требования корректности, предъявляемые к работам

1. Проверка контрактов функций.

На мой взгляд, чуть ли не единственной обязательной к использованию парадигмой и доступной во всех языках программирования, выше ассемблера, является [5]. Ключевые понятия: предусловия, постусловия и инварианты. В языках C/C++ соблюдение контрактов контролируется с помощью assert, возведения сигналов, бросания исключений.

Данный подход является обязательным, потому что гарантирует максимально раннее обнаружение проблемы. Игнорирование подхода приводит к проблемам в самых неожиданных местах. Программа может вести себя как корректная, хотя содержит в себе большое количество ошибок, пока очередная проблема не приведет к лавине. Примеры неожиданно всплывших проблем: https://software-testing.ru/library/testing/general-testing/2082-horrible-bugs

Требуется производить проверку контрактов:

  • предусловий: с помощью assert в начале функции. Пример: Указатель: может быть нулевым? Нет? assert.
  • инвариантов: по ходу выполнения функции. Пример: по ходу алгоритма нужно произвести удаление элемента из контейнера. Можно проверить, что этот элемент вообще присутствует в контейнере.
  • постусловий: В конце функции и с помощью тестов. Пример: в результате удаления вершины из списка она не должна быть достижима из головы списка. Это можно провалидировать с помощью assert.

Проверки контрактов упрощают восприятие кода и могут при определенном стечении обстоятельств заменять документацию.

Q: но ведь проверка контракта может быть медленной, теряем производительность

A: используйте assert для медленных проверок, если нужна производительность - используется release режим сборки, туда эти проверки не попадут

Q: а что делать с malloc, который вернет NULL, это же и в release может случиться

A: сейчас у нас в арсенале нет способа просто решить эту проблему. Можно, но сложно. Поэтому ставим assert, чтобы развивать привычку. В заданиях незачем аллоцировать Очень Много Памяти, поэтому проблема не слишком актуальная. "В жизни" будет использоваться не assert, а какой-то другой механизм.

2. Стиль кода совпадает во всем проекте.

Либо везде табуляции для отступов, либо везде пробелы. Именование переменных либо везде snake_case, либо везде camelCase. Желательно отделять разные сущности друг от друга написанием. Например, можно выделять:

  • МАКРОСЫ()
  • КОНСТАНТЫ
  • функцииКоторыеЧтоТоДелают()
  • переменныеКоторыеЗачемТоПонадобились
  • ИменаКлассовИСтруктур

Допустимы конфликты написания (когда по имени нельзя однозначно восстановить, какая это языковая конструкция), но не стоит для всего использовать одно и то же написание.

3. Каждая строчка кода что-то делает.

Если строчку кода можно удалить без изменения поведения программы, значит, ее нужно удалить.

4. В языке Си использовать void(void), а не void().

5. Отсутствие утечек памяти, Undefined Behaviour и других проблем Ваша программа должна компилироваться и корректно завершаться, а соответствующая утилита не должна находить какие-либо проблемы:

  • в debug и release (-DNDEBUG) режимах сборки
  • будучи запущенной из-под gdb
  • будучи запущенной из-под valgrind
  • после компиляции с -fsanitize=address
  • после компиляции с -fsanitize=undefined
  • после компиляции с -fsanitize=leak
  • на разных версиях разных компиляторов (gcc, clang, msvc, ...)

включая комбинации этих опций, кроме случаев ошибок в самих утилитах и несоответствия компилятора Стандарту. (Да, и то, и то бывает.)

6. Именование должно быть понятным кому угодно, не только лишь Автору, но и всем. Имя функции должно отражать особенности ее поведения. Имя переменной должно быть говорящим. Лучше всего, если вне контекста можно понять, за что отвечает / что делает та или иная функция, структура, переменная. Стандарт языка в этом смысле не является примером для подражания.

Этот раздел не может быть формализовать и ложится целиком и полностью на здравый смысл. Здесь зачастую нет "идеального" решения, и если вам не удается достичь совершенства - не расстраивайтесь, таких как вы - легион. Проблема именования является одной из ключевых в программировании, вызывает большое количество дискуссий, требует много времени и внимания разработчиков.

7. Если была допущена какая-то проблема в реализации функции, должен быть написан тест, детектирующий эту проблему.

8. Использование сторонних функций только из стандартной библиотеки языка, если противное явно не оговорено в задании.

В частности, написание C/C++ кода без привязки к платформе Linux; отсутствие POSIX-специфичных функций и структур. Не смотря на то, что целевая платформа - Linux, привычка писать платформенно-специфичный или компиляторо-специфичный код может однажды сыграть злую шутку. Комитет по стандартизации стремится к тому, чтобы не было необходимости писать такой код, включая новые и новые платформенно-независимые вещи в стандарт языка. Если "совсем никак", то лучше обернуть специфичную функцию: сделать size_t mygetline(char **lineptr, size_t *n, FILE *stream) {return getline(lineptr, n, stream);}

9. Не использовать exit где-либо, кроме main.cpp Представьте, что вы пользуетесь какой-то библиотекой. Позвали функцию из нее, а ей что-то не понравилось (не хватило памяти, запись на диск не удалась, ...) и она позвала в своих недрах exit. Это полная катастрофа, потому что на этом работа программы завершается, а в месте вызова вы об этом даже не узнаете. Это может быть очень болезненно: может быть, в оперативной памяти хранится результат длительных вычислений, или ценная информация, это могли быть данные, за которые нужно платить (обращения к платным сервисам) и много что еще. Главное - за вас кто-то другой решил, как программа должна себя вести. Никогда не используйте exit в библиотеках. Во всех заданиях прослеживается структура: сделать некоторый набор действий в рамках некоторой модели. Модель находится в одном месте, управляющая логика - в другом. Здесь проходит условная черта: модель - это библиотека, управляющая логика - конечный пользовательский код. Обработка ошибок должна осуществляться с помощью кодов возврата в языке Си и с помощью кодов возврата либо исключений в языке Си++. В языке Си разрешается делать exit в том случае, если гарантированно освобождены все ресурсы (позваны free, fclose, ...). Например, разрешается в main обработать ошибки так: передать код возврата в специальную функцию, которая по коду возврата напечатает сообщение и позовет exit. Но делать так не рекомендуется, потому что эквивалентным образом можно позвать return из функции main:

void exitOnError(int ec) {...; exit(ec);} int main(){...; if (ec) {exitOnError(ec);}

vs

int printError(int ec){...; return ec;} int main(){...; if(ec) {return printError(ec);}


To be continued.

Практика 2: Свиридкин

Практика 3: Лапшин

Практика 4: Гулецкий