Годовой образовательный курс лаборатории Intel «Введение в промышленное программирование на языках С, С++ и структуры данных» для студентов 1 курса ФРТК, 3 ч. лекций + 4 ч. семинаров в неделю И. Р. Дединский, ст. преп. каф. информатики МФТИ Пояснительная записка Курс разработан для студентов 1 курса ФРТК МФТИ, обучающихся в лаборатории Intel1. Цель курса – научить студентов современным методам программирования и разработки программных систем на языках С и С++, привить навыки надежного, промышленного программирования, работы в команде, подготовить их для участия в тематических проектах второго курса ILab. Преподавание курса ведется в предположении, что студенты уже знают язык Паскаль или аналогичный процедурный язык. Курс разбит на 2 части: Первая (5-6 занятий) – быстрое практическое введение в С через разбор и решение большого количества небольших задач, заканчивающееся потоковой контрольной работой с автоматической проверкой. Вторая (11-12 занятий) – введение в структуры данных и алгоритмы, практическая часть которой содержит меньшее число задач, но большего объема. Задачи второй части подобраны по большей части таким образом, что в конце курса каждый студент самостоятельно реализует примитивную модель вычислительной системы (стековой виртуальной машины), инструментальные средства низкоуровневой разработки для него (ассемблер и дизассемблер), а также примитивный высокоуровневый транслятор (проект «нано-GCC»), совместимый с трансляторами других студентов на уровне AST. Это дает возможность использовать кросс-компиляцию программ одного студента для виртуальной машины другого (ngcc program.alice -m bob) затем выполнение на соответствующей виртуальной машине (vm_bob program.bob), а также перевод в исходный текст в формате языка другого студента (ngcc program.alice --translate bob, ngcc program.bob --translate alice). Третья часть курса (6-10 занятий) представляет собой введение в язык С++ в терминах различий С и С++, методом рефакторинга ряда решений на языке С, рассматривавшихся в осеннем семестре. Четвертая часть (6-10 занятий) посвящена технологии применения С++ (ООД, ООП, компонентное программирование) в многомодульном проекте, использующем программный код группы разработчиков в виде динамически подключаемых библиотек. В четвертой части, используя материал курса языка Ассемблера кафедры информатики МФТИ, появляется возможность устроить продолжение тематики предыдущего курса по моделированию вычислительных систем, в виде реализации простейшего JIT-компилятора. Сложность задач курса легко регулируется их функциональным наполнением (простейший вариант – транслятор формул, используемый для построения графиков элементарных функций). Для обучения используются следующие принципы: 1. Во главу угла ставится задача, ее решение и, главное, путь от задачи к решению. Во всякой задаче подчеркивается разделение на идею решения и технологию реализации. 2. Самостоятельность решения является ключевым условием. 3. Понимание студентами тех средств, с помощью которых он решил задачу, ставится выше уровня самих средств решения. 4. Аккуратность и надежность решения ставятся выше «программистских трюков», иногда позволяющих в отдельных случаях добиться несколько лучших результатов. Для целостного освоения теоретической информации и овладения практическими навыками курс основывается на принципах системного подхода и рассматривается как последовательность усвоения когнитивно-технологических единиц, как единиц действительного усвоения знаний, определенных следующим образом: 1. Зачем это надо, 1 Здесь и далее выделение полужирным шрифом подразумевает текст, относящийся к обязательной части курса. Текст без выделения относится к факультативным (хотя крайне желательным для примененияи и проведения) принципам, подходам, разделам и темам. И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса 2. 3. 4. 5. 6. 7. 8. 2 Что это такое, На чем основано и с чем связано, Как это применять, Где это можно и где нель-зя использовать, Чем придется пожертвовать, Что будет, если этого не делать, Какие в этом «подводные камни» (чего опасаться). Лекционная часть занятий не ставит перед собой цели повторить или механически расширить ни курс лекций по информатике для 1-2 курсов МФТИ, ни классические учебники по языку Си (Б. Кернигана и Д. Ритчи и другие). Наоборот, в лекционной части делается акцент на границах применимости знаний, методик и примеров их практического применения, особенно тех, которые отсутствуют в академической литературе, с учетом принципов и реалий промышленного программирования. Теоретические и практические сведения тесно переплетаются с советами по их использованию и анализом побочных эффектов, даются в тесной связи с примерами их практического удачного и неудачного применения. Это существенно отличает данный курс от множества других. В начале курса этот акцент наиболее выражен, так как формирует необходимую систему ценностей, в дальнейшем активно использующуюся. По мере выработки этой системы способ изложения материала становится более традиционным, хотя все равно остается его направленность на решения конкретных задач, но со значительным уровнем обязательного обобщения и рефлексии после изучения темы, что не позволяет курсу превратиться в натаскивание на типовые рабочие приемы кодирования. Во втором семестре значительное время уделяется разбору конкретных примеров объектно-ориентированного дизайна разного качества проектирования, что занимает значительное учебное время. В обучении активно используется менторская система, с помощью которой на практике разбираются темы, приемы и методы, упоминаемые на лекциях. Сдача работ студентами осуществляется через помещение его на серверный репозиторий курса. Основная форма проверки кода менторами – детальный code review с разбором типичных случаев на групповых занятиях. Со второй части курса вводится peer review. Результатом согласованной работы лектора, менторов и студентов должно стать не только изучение теоретического материала и отработка его на практических задачах, но и формирование современной методологии разработки программ и профессиональной системы ценностей у студентов, что позволит им успешно участвовать в тематических курсах второго года обучения в Лаборатории. Содержание курса (по семестрам) I семестр № 1. Содержание темы Введение в язык С. Краткая история и особенности возникновения языка. Причины, вызвавшие бурное развитие Си и появления его многочисленных потомков. Язык Си и ОС Unix. Ключевая роль Си в программировании, ориентированном на производительность и проблемы, проистекающие из этого. Ключевая роль Си в обучении программированию. Высокоуровневые и низкоуровневые языки. Происхождение низкоуровневых языков («от машины»), их непосредственная связь с архитектурой вычислительной системы, возможностями и набором команд процессора. Языки ассемблера. Примеры трансляции простейших алгоритмов на низкоуровневых языках. Эффективность низкоуровневых программ. Особенности и проблемы разработки программ на низкоуровневых языках. Оценка пригодности низкоуровневых языков для реализации задач разного масштаба. Переносимость низкоуровневых программ. Высокоуровневые языки. Происхождение высокоуровневых языков («от человека»). Особенности разработки программ на высокоуровневых языках. Переносимость высокоуровневых программ. Проблемы с производительностью и доступом к вычислительным средствам из языков высокого уровня. Понятие среды исполнения программ для языка. Понятие о языках сверхвысокого уровня. Си как язык промежуточного уровня, задуманный и построенный как компромисс между низким и высоким уровнем. Обратная сторона такой промежуточности. Проблемы с переносимостью программ на Си и примеры их избегания. Понятие оптимизации программ. Возможность компромиссного баланса между производительностью и переносимостью, его контроль со стороны программиста. Машинная и ручная оптими- К-во часов Обяз. Доп. 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса 3 № Содержание темы К-во часов Обяз. Доп. зация. Плюсы и минусы оптимизации, вред преждевременной оптимизации. Командная разработка проектов. Системы контроля версий, репозитории и работа с ними. Системы контроля версий SVN, Mercurial и Git. Code review в системе контроля версий. Типичный рабочий процесс при работе с системой контроля версий SVN и репозиторием Google Code в Linux и Windows. Рабочий процесс для code review в ILab. 2. Трансляция и построение программы. Понятие программного проекта. Модель проекта «один исходный файл – одна программа». Трансляция файла с исходным текстом. Соотнесение исходного текста и исполняемого кода программы. Модель проекта «много исходных файлов – одна программа». Понятие раздельной компиляции. Достоинства и недостатки раздельной компиляции. Проблема контроля доступа к данным и коду, содержащимся в исходном файле, из других исходных файлов. Понятие о включаемых заголовочных файлах, информация, содержащаяся в них, ее использование в процессе трансляции. Необходимость единого промежуточного формата (формата объектных файлов). Роль объектных файлов в процессе трансляции. Понятие линкера (редактора связей), его роль в формировании исполняемого файла. Понятие библиотеки как объединения объектных файлов. Понятие стандартной библиотеки. Компоненты стандартной библиотеки: множество заголовочных файлов и множество статически линкуемых библиотек. Подключение сторонних библиотек. Введение в язык Си на примере программы, решающей квадратное уравнение, вначале на примерах и способах, знакомых начинающим слушателям по школьным и олимпиадным курсам. «Традиционный школьный» подход: одна функция, минимум проверок, максимум неявных условий корректного использования программы. Реализация программы квадратного уравнения на языке Си. Структура и синтаксис простейшей программы на языке Си. Раздел включаемых заголовочных файлов, главная программа. Объявление переменных, типы данных (на примере int и double). Ввод и вывод информации, функции printf и scanf. Работа функции scanf, необходимость передавать ей местоположение (машинный адрес) переменной. Оператор взятия адреса переменной. Арифметические выражения, операторы сложения, вычитания, умножения и деления, оператор присваивания. Функция вычисления квадратного корня. Условный оператор. Возвращаемое значение главной функции программы. Разбор традиционных очевидных проблем, понятных знакомым со школьным и олимпиадным подходом: учет области определения функции квадратного корня, особенности сравнения действительных чисел, учет сводимости квадратного уравнения к линейному. Доработка программы с устранением этих проблем и доведением качества алгоритма (не кода) до «олимпиадного». Функция fabs для вычисления модуля действительного числа. 3 3 3. Реализация принципов промышленного программирования применительно к программе решения квадратного уравнения. Понятие рефакторинга в инженерной деятельности. Си как язык промышленного программирования. Критический анализ и рефакторинг программы квадратного уравнения с учетом ценностей промышленного программирования. Форматирование кода, его важность для реализации надежности и командности, его виды, понятие корпоративного стиля форматирования. Примеры правил расстановки пустых строк, пробелов, отступов, линий-разделителей. анализ происхождения этих правил. Ограничение длины строк кода, его влияние на количество ошибок в программе. Вывод в начале программы ее названия, краткого описания, автора, версии и даты создания. Именование переменных, хорошие и плохие имена, примеры проблем с именованием переменных. Пользовательский интерфейс программы, построение диалога с пользователем. Сервер автоматического тестирования как особый вид пользователя, необходимость построения компромисса между диалогом с пользователем-человеком и пользователем-сервером. Реализация повторного использования кода в программе квадратного уравнения. Рефакторинг с выделением функции решения квадратного уравнения. Понятие определения функции, ее заголовка и тела. Качество имени функции, стили его образования (pascal case, camel case). Формальные параметры функции квадратного уравнения (коэффициенты), синтаксис их объявления. Понятие и синтаксис вызова функции. Понятие прототипа функции, 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 4 К-во часов Обяз. Доп. 3 3 его необходимость для контроля вызова, синтаксис. Понятие архитектурного рефакторинга, программа как система из взаимодействующих функций, модульный принцип построения программ. Роль архитектуры кода в обеспечении масштабируемости и командной работы. Критический анализ получившейся функции, ее недостатки (печать результатов внутри функции). Анализ и классификация результатов функции (количество корней, их величины). Способы передачи данных из функции. Передача данных через оператор return. Анализ возвращаемого значения в main, оператор switch, его синтаксис. Проблема передачи информации о бесконечном количестве корней. Использование «магических чисел» и именованных констант для обозначения бесконечного количества корней. Синтаксис определения именованной константы. Указатели, передача данных через параметр-указатель. Входные и выходные параметры функции. Опасности работы с указательными параметрами, примеры их неверного использования. Необходимость проверки параметров вызываемой стороной. Макрос assert, его применение для проверки указательных параметров, информативность для программиста при отладке. Понятие контрактного программирования. Принципы контрактного программирования на примере взаимодействия функции квадратного уравнения и функции main (проверка параметров в функции решения уравнения и проверка возвращаемого значения в main. Синтаксис метки default). Рекомендации по количеству строк кода функции. Использование и проектирование функций, метод проектирования «сверху вниз», как минимизирующий проблему декомпозиции. Заключительный обзор стратегий, примененных при рефакторинге: основной – «разделяй и властвуй», его следствия – функциональная декомпозиция (модульный принцип, структурное разделение), контрактное программирование (разделение ответственности). Общая стратегия работы инженера: мечтатель – реалист – критик (У. Дисней). 4. Комментирование кода. Задачи комментария в коде, его правильное и неправильное применение. Тривиальные, неясные, устаревшие и неверные комментарии. Использование комментариев только для того, что нельзя непосредственно выразить через текст программы. Примеры: комментарии «о хаках», комментарии-TODO. Блочные комментарии файла и функции. Документирование текста программы. Системы автоматического документирования. Система документирования doxygen. Документирующие комментарии doxygen, основные теги doxygen (file, mainpage, author, version, date, note, warning, par, param, param[out], return, see, code/endcode). Постфиксные документирующие комментарии. Порождение выходных форматов, пример создания документации. Выработка привычки создавать документацию и считать недокументированный исходный текст неполноценным. Динамическая верификация кода (assert). Препроцессор языка C. Соотношение фаз работы компилятора и препроцессора. Директивы препроцессора. Директива include для стандартных заголовочных файлов и файлов из произвольных директорий. Опасность путаницы включаемых файлов с одинаковыми названиями. Использование одинаковых названий для реализации особенностей компиляции (#include “config.h”). Директива define. Использование директивы для задания констант, ее отличия от конструкции с const. Директива define с параметрами. Границы имени и определения в случае макроопределения с параметрами. Особенности и побочные эффекты в случае макроопределения с параметрами, ее отличие от функций. Классические примеры построения макроопределения с параметрами с демонстрацией побочных эффектов и защитой параметров скобками. Продолжение макроопределения на следующие строки с помощью символа обратной косой черты. Опасности применения этого символа (конструкция \ с пробелом после него, продолжение комментария с помощью \). Типичные ошибки построения макроопределения: лишние точки с запятой, несинтаксические макроопределения, не похожие на вызов функции, отсутствие скобочной защиты параметров и всего макроопределения. Ошибки применения define: использование аргументов с побочным эффектом (инкремент/декремент переменных, вызовы функций, работающих с потоками и т.п.) Временные переменные в определениях, опасность смешения и сокрытия имен разных областей видимости, передача имени типа в качестве параметра, ключевое слово auto в С++ 0x11. Директива undef. «Ситуационные» «локальные» макроопределения. Стратегия применения макроопределений: они хуже всего остального, что есть в языке Си, но лучше прямого копирования текста программы. Построение макроопределения assert. Стан- И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса 5 № Содержание темы К-во часов Обяз. Доп. дартные макроопределения __FILE__, __LINE__, __DATE__, __TIME__. Макроопределения __PRETTY_FUNCTION__, __FUNC__ и им подобные, константа __func в С++. Макрооператор #. Понятие о макрооператоре ##. Необходимость управления статусом проверок в макроопределении assert, роль макроопределения NDEBUG, его правильное применение. Разбор механизма влияния NDEBUG на assert, условная компиляция. Директивы #ifdef, #ifndef, #if (и синтаксис допустимых в нем конструкций), #else, #elif, #endif. Построение собственного полноценного улучшенного макроопределения ASSERT. Задача о подавлении вывода отладочной информации. Построение макроопределения, блокирующего (регулирующего) вывод отладочной информации в зависимости от компиляции на клиентской или серверной стороне тестирующей системы (DBG_PRINTF и т.п.). Использование этого макроопределения для работы над единой версией текста программы, предназначенного как для автоматического тестирования, так и для взаимодействия с пользователем. Тонкие вещи в макроопределениях: фазы раскрытия макроопределений, раскрытие макроопределений, являющихся параметрами других макроопределений. Пример с автоматическим «закавычиванием» автоматически подставляемого номера строки. Другие директивы препроцессора: error, warning, pragma. Примеры использования этих директив. 5. Тестирование программ. Тестирование с помощью специальных программ (серверов). Запуск программы с переназначением ввода-вывода. Краткий простейший синтаксис командной строки (операторы переназначения входного и выходного потоков, оператор канала). Простейшая техника построения собственного тестирующего «сервера», основанного на файлах с исходными данными и файлах с эталонными ответами. Самотестирование программы (модуля). Понятие о юнит-тестировании. Сравнение юнит-тестирования и классического ручного тестирования. Построение набора тестов: тесты на типичные, тривиальные и граничные случаи. Использование препроцессора для юнит-тестирования функций. Важность юнит-тестирования для последующего повтороного использования, рефакторинга и редизайна функций и модулей. Использование библиотек и проектов для юнит-тестирования. Массивы в языке Си. Использование массивов для хранения серий данных. Объявление и инициализация массива. Ограничения массивов в Си (нумерация, единство типа данных, ограниченный размер). Хранение массивов в оперативной памяти. Адресация к массиву. Имя массива как адрес (указатель) его начального элемента. Типичные ошибки при работе с массивами (выход за границы массива). Отсутствие автоматической проверки выхода за границы массива в Си, причины этого отсутствия. Возможные последствия выхода за границы массива по чтению и по записи. Важность хорошо аннотированных диаграмм для разбора вопросов, связанных с адресацией и массивами. Проверка допустимости индексации с помощью assert. Надежный способ написания двойных неравенств в Си («почти как в математике»). Внешние программные средства отслеживания ошибок адресации во время выполнения программы (GNU valgrind, MicroFocus (NuMega) Bounds Checker). Проблема дублирования размера массива в разных частях программы, решение ее с помощью именованных констант. Оператор sizeof. Вычисление длины массива в его элементах с помощью sizeof, макрос на основе этого подхода, границы его применимости (только при наличии в текущей области видимости полного описания с указанием размера). Особенности работы с массивами с размером, не являющимся константой времени компиляции. Передача массива в функцию, проблемы, с ней связанные (волатильность из-за отсутствия передачи по значению, потеря атрибута размера массива). Решение проблемы волатильности с помощью модификатора const. «Неверная» работа sizeof в случае передачи массива в функцию, решение этой проблемы с помощью явной передачи размера массива. Экономия на передаче длины массива: использование стоп-значений, удобство и опасности такого подхода. «Паскальный» подход (хранение длины массива в его начальном элементе), его ограничения. 3 3 6. Динамическая память в языке Си. Структура адресуемой памяти процесса. Размещение переменных программы, пример размещения. Понятие «свободной памяти». Функции работы с динамической памятью. Время жизни блока динамической памяти. Динамическая память как ресурс, работа с исчерпанием памяти, реализация стратегий гарантированного 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 6 К-во часов Обяз. Доп. освобождения. Реализации систем динамической памяти, стратегии поиска свободного блока, время поиска блока, фрагментация динамической памяти. Пример структуры блока динамической памяти. Последствия выхода за границы блока, двойного освобождения блока, переполнения буфера, находящегося в динамической памяти. Реаллокация блоков динамической памяти, проблема пересчета указателей в случае изменения адреса блока. Стратегии эффективной работы с динамической памятью для высоконагруженных приложений. Аллокация фиксированными блоками, реализация альтернативных аллокаторов, учитывающих специфику размеров и времен использования блоков. Оптимизация аллокаций для малых блоков. Указательная арифметика. Операции с указателями в языке См. Формула вычисления адреса для доступа к элементу массива. Тождественность различных обозначений для доступа к элементам массива. Коммутативность в алгоритме доступа и ее синтаксические проявления (для лучшего понимания процесса индексации). Использование указательной арифметики для потоковых вычислений на массивах, понятие «текущего элемента». Хранение элементов разных типов в одном массиве, задачи, в которых возникает такая необходимость, особенности синтаксиса и адресации. 7. Многомерные массивы, их объявление, инициализация, адресация в них. Проблема передачи многомерного массива в функцию. Вычисление адреса элемента в многомерном массиве. Необходимость передавать размеры многомерного массива в функцию. Самостоятельное вычисление смещения относительно начала массива и адреса нужного элемента массива, преимущества и недостатки такого подхода. Функции или макросы для адресации к массивам в случае использования самостоятельно вычисленных смещений. Реализация непрямоугольных массивов с регулярной структурой на примере «треугольного» массива. Реализация многомерных массивов в динамической памяти, доступ к таким массивам. Массивы указателей. Синтаксис объявления и использования массивов указателей. Трактовка операции индексации в случае массивов указателей. Реализация многомерных массивов через массивы указателей, преимущества и недостатки по сравнению с традиционными многомерными массивами. Решение вопроса о хранении массива с «рваным правым краем» (неодинаковым размером строк). Использование разных блоков для хранения разных строк массива, возможность реаллокации для изменения длин строк. Использование одного блока динамической памяти для хранения данных массива в случае, если изменения длин строк не предполагается. Использование единственного блока динамической памяти для хранения массива указателей и данных. Реаллокация динамических многомерных массивов, реализованных как массивы указателей, необходимость пересчета указателей. 3 3 8. Строки. Реализация строк в языке Си, «смысловая» и «свободная» зоны строки, преимущества и недостатки такого подхода, сравнение с другими реализациями (паскальные строки, структуры с явным хранением длины). Нулевой символ. Понятие пустой строки. Задачи о копировании и сравнении строк, задача о сжатии пробелов в строке «на месте». Концепция «текущего символа». Проблемы «маляра Шлемиля (Шлемиэля)», их характерные проявления и устранение. Возможности строковой библиотеки языка Си. Массивы строк. Реализация массива строк с «рваным правым краем», и его формирование при чтении текста из файла. Задача о сортировке строк, сравнение эффективности ее реализаций для различных реализаций обмена строк. Различные критерии сортировки строк. Функция сортировки строк по различным фиксированным критериям. Обобщение алгоритмов сравнения строк. Указатели на функции. Использование указателей на функции для построения универсальной функции сортировки строк. Библиотечная функция qsort и работа с ней. Работа с файлами. Функции открытия и закрытия файла. Текстовые файлы, посимвольное и построчное считывание. Состояние «конец файла», константа EOF. Опасность переполнения буфера при чтении. Форматированный текстовый ввод и вывод, опасности, с ним связанные. Символы преобразования данных и форматирования. Буферизация, ее виды. Блочные чтение и запись. Бинарные файлы. Отличия в обработке текстовых и бинарных файлов в Windows. Представление данных в бинарных файлах для раз- 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса 7 № Содержание темы К-во часов Обяз. Доп. ных типов данных. Форматы хранения целых чисел. Перемещение по файлу. Буферизация средствами операционных систем, работа с сетевыми файлами и файлами на медленных носителях. Отображение файлов в память, особенности работы с такими файлами, удобства и «подводные камни». 9. Графические средства языка С (SDL, CImg, TXLib, OpenGL (Glut) и другие). Использование графических средств для визуализации. Инициализация визуализации, основные графические примитивы. Архитектура программ, использующих визуализацию. Понятие модуля отображения данных, архитектура «model-view», понятие об архитектуре MVC. Нестандартность графических средств и их платформозависимость. Обеспечение кроссплатформенности при визуализации данных. Понятие о послойной архитектуре программ. Структуры. Сравнительный анализ структур и массивов, мотивированный выбор для представления данных в виде структур или массивов. Операции доступа к структурам. Построение структур, размер структуры, выравнивание полей. Передача структур в функцию, способы ее ускорения (через указатель) и защиты доступа (через указатель на константные данные). Структура как одно из объектно-ориентрованных понятий в языке Си. Проблемы и примеры логичного и нелогичного построения структур. Связь структуры с семейством функций для ее «обслуживания». Реализация «методов класса» средствами языка Си. 3 3 10. Понятие абстрактных структур данных. Примеры работы структур данных в различных алгоритмах. Структура данных «стек». Функции для работы со стеком. Проблема допустимости значений и согласованности переменных в структуре данных, связанные с ней опасности и решения. Функции конструирования, уничтожения, верификации и технической распечатки (дампа). Построение функции верификации как предиката. Пример реализации функции дампа. Реализация верифицируемой функции для работы со структурой данных. Понятие о предусловии, постусловии и инварианте алгоритма. Реализация предусловий с помощью функции верификации и дампа. Тактика двойной верификации с предусловием и постусловием для функций, работающих с неконстантным объектом. Понятие ошибочного объекта. Введение объекта в фатальное ошибочное состояние, понятие «яда», его использование для предотвращения работы с уже уничтоженным объектом. Понятие о нефатальной ошибке, способы ее реализации в языке Си (возврат кодов ошибок, создание переменных, хранящих коды ошибок, написание функции-обработчика ошибок, хранение указателя на функциюобработчик ошибок). Использование стека. Задача о вычислении выражений. Вычисление выражений, заданных обратной польской записью. Понятие стекового вычислителя (процессора). Реализация структуры стекового вычислителя и связанных с ней функций. Реализация арифметических команд для стекового вычислителя. Примеры работы стекового вычислителя. Интерактивный режим работы программы вычисления выражений. Задача о построении таблицы значений функции или ее графика. Пример программы в обратной польской записи (Р-программы) для вычисления значения функции в каждой заданной точке. Пример фрагмента программы на языке Си для запуска стековых вычислений функции для каждого заданного значения ее аргумента. Необходимость использования аргумента функции (абсциссы) в обратной польской записи, проблема его хранения в вычислителе. Понятие регистра вычислителя (процессора), введение регистра абсциссы (АХ) в стековый вычислитель. Функция на языке Си для загрузки значения абсциссы в вычислитель (mov_ax). 3 3 11. Проблема скорости работы стекового вычислителя при исполнении стереотипного кода для каждого заданного значения абсциссы. Практическая невозможность сохранения интерактивной реализации. Реализация программирования вычислителя с помощью текстового файла с последовательностью команд в обратной польской записи. Анализ скорости работы такой программы, определение «узких мест» для повышения эффективности. Введение нумерации команд (Р-кода) для повышения эффективности. Решение проблемы низкой мнемоничности Р-кода на стороне языка Си с помощью констант и конструкции enum. Решение ана- 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 8 К-во часов Обяз. Доп. логичной проблемы при написании программ на Р-коде с помощью реализации транслятора в Р-код (ассемблера). Реализация дизассемблера для целей отладки. Сопряженность программ исполнителя, ассемблера и дизассемблера, ее реализация с помощью единого включаемого файла с именами команд и константами, задающими операции Р-кода. Использование препроцессора для автоматического сопряжения имен констант, их значений и связанных с ними алгоритмов в программах вычислителя, ассемблера и дизассемблера. Хранение программы в Р-кодах в массиве команд, преимущества такого подхода перед постоянным чтением их из файла. Отделение фазы загрузки программы из файла с Р-кодом в массив команд от фазы исполнения программы. Переход к бинарному представлению программ с Р-кодом. Понятие заголовка исполняемой программы, его реализация для программ с Р-кодом (сигнатура, номер версии системы команд). Реализация эффективного программного комплекса стекового вычислителя для построения графиков или таблиц значений функций и системных утилит для него. 12. Расширение круга задач для стекового вычислителя. Обобщение постановки задачи для построения таблиц значений (графиков) функций с необходимостью единственного исполнения программы и организации перебора значений аргумента в Р-программе. Пример возможной Рпрограммы для построения таблиц значений (графика) функции. Необходимость команд текстового или графического вывода данных (out), команд условного и безусловного переходов (j*, jmp) для организации цикла в Р-программе. Проблема аргумента в командах переходов. Реализация команд переходов с помощью вручную рассчитанных адресов переходов, недостатки такого подхода. Задача автоматического расчета адресов переходов. Понятие меток как синонимов адресов. Задача сканирования меток и сопоставления им адресов. Методы сопоставления (патчинг кода и многопроходная трансляция). Реализация программы многопроходного (двухпроходного) транслятора в Р-код с использованием нумерованных меток. Именованные метки и их преимущество перед нумерованными. Обсуждение типичных ошибок в реализации команд переходов. Реализация вызова функций в стековом вычислителе. Сравнение работы команд вызова функции и безусловного перехода. Понятие возврата из функции. Необходимость в регистре, хранящем адрес возврата из функции, или стеке адресов возвратов для поддержки рекурсии. Реализация команд вызова функции с аргументом в виде метки и возврата из функции с помощью отдельного стека для хранения адресов возврата. Выполнение задач на стековом вычислителе в виде написания самостоятельно разработанных Р-программ (решение квадратных уравнений с разбором всех частных случаев, выдачей количества и величин их корней, вычисления факториала чисел и чисел Фибоначчи итеративным и рекурсивным способами). Расширение количества регистров (добавление регистров bx, cx, dx), системы команд (добавления команды ввода с клавиатуры in). 3 3 13. Структура данных «список». Использование списков. Односвязные и двусвязные списки. Реализация списков с хранением в массиве и с хранением в виде отдельных узлов. Сравнительная характеристика этих реализаций. Списки как потоковая структура данных. Неэффективность операции индексации для списков (еще один пример «алгоритма маляра Шлемиля»). Кольцевые списки, их реализация через линейные и наоборот. Задача о проверке зацикленности списка. Хранение длины списка в явном виде, достоинства и недостатки этого подхода. Проблема дублирования (кэширования) данных и необходимость верификации дубликатов. Проверка валидности списка, уровни глубины этой проверки. Разработка юнит-тестов для списков. Структура данных «хеш-таблица». Задачи, приводящие к хештаблицам. Хеш-функции, их примеры (от простейших и бесполезных к реальным) и свойства, качество хеширования. Характерные размеры хеш-таблиц. Использование хеш-таблиц. Юнит-тестирование хеш-таблиц. Качественное сравнение качества хеширования с помощью гистограммы заполнения хеш-таблицы. Использование хеш-таблиц для эффективного поиска перевода слов в словаре. Генерация файла HTML с подстрочным пе- 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса 9 № Содержание темы К-во часов Обяз. Доп. реводом текста. Программа «Подстрочный перевод». 14. Структура данных «дерево». Примеры различного использование деревьев. Деревья поиска. Перечисление узлов дерева, виды обходов дерева. Верификация деревьев и ее ограниченность (или необходимость добавления дублирующихся данных). Дамп деревьев. Задачи, использующие деревья. Структура арифметических выражений. Инфиксная форма записи выражений, ее соответствие порядку действий и дереву вычислений. Задача о грамматическом разборе выражений. Необходимость задания структуры выражений. Понятие языка и грамматики. Способы построения дерева разбора. Алгоритм распознавания языка методом рекурсивного спуска. Итеративное построение грамматики языка программирования и кода распознавателя, использующего рекурсивный спуск. Решение вопроса о приоритете операторов и задачи о подвыражениях в скобках. Достоинства метода рекурсивного спуска, его проблемы и ограничения. Различные обходы деревьев выражений, восстановление линейной инфиксной записи и генерация различных видов польских записей (префиксной, постфиксной). Транслятор инфиксных выражений в ассемблер стекового процессора. Лексический анализ как предварительная фаза перед синтаксическим, его роль в повышении эффективности трансляции и ее упрощении. Понятие лексемы, ее реализация. Рефакторинг транслятора с применением лексического анализа. Автоматизация построения трансляторов. Понятие о промышленных системах построения трансляторов, примеры их использования. 3 3 15. Архитектура nGCC. Front-end, middle-end и back-end. Достоинства модульного принципа и общего внутреннего формата. Задача о групповой реализации nGCC для n модельных входных языков высокого уровня и m вычислителей с разными системами команд (в данном случае n = m = k, где k – количество студентов в менторской группе). Разработка общего внутригруппового стандарта промежуточного файла с AST, поддерживающего дополнительные данные (имена переменных и т.п.). Рефакторинг транслятора инфиксных выражений с использованием архитектуры nGCC. Реализация визуализатора AST. Реализация программы для запуска частей транслятора (драйвера). Реализация обратного преобразования (из AST в код модельного высокоуровневого языка). Работа с переменными в модельных высокоуровневых языках. Модель оперативной памяти высокоуровневого языка. Использование таблиц имен для переменных и других именованных сущностей. Реализация операторов присваивания. Реализация условных операторов, операторов цикла, вызова функции. 3 3 45 45 Всего за 1 семестр II семестр № Содержание темы К-во часов Обяз. Доп. 1. Преобразование структуры выражения. Выражения, эквивалентные в смысле вычисления. Примеры преобразования выражений. Символьное дифференцирование. Реализация символьного дифференцирования как обход дерева выражения с построением нового дерева. Реализация функции дифференцирования и ее сходство с таблицей производных элементарных функций и правил дифференцирования. Дифференцирование сложной функции. Сложность записи построения результатов (правил) дифференцирования, использование препроцессора в качестве DSL для записи правил символьного дифференцирования. Реализация «оператора символьного дифференцирования». Символьное дифференцирование функции (формулы) от нескольких переменных, получение полного дифференциала. 3 3 2. Оптимизации над выражением. Недостатки деревьев выражений, построенных при символьном дифференцировании. Простейшие оптимизации (удаление ненужных операций с нулем и единицей, свертка кон- 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 10 К-во часов Обяз. Доп. стант). Задача о приведении подобных слагаемых, способы ее решения. Вариант решения с частичным обходом дерева и сортировкой переменных. Вариант решения с созданием словаря подвыражений. Понятие о действиях (операторах), зависимых по данным. Преобразования последовательностей операторов, независимых по данным. Задача о «перемешивании» кода программы с сохранением корректности алгоритма. Задача об обфускации программного кода. Реализация обфускатора модельного высокоуровневого языка. 3. Предпосылки разработки С++ и мотивация его авторов. Современный характер разработки ПО. Критический анализ языка С при использовании в больших многокомпонентных проектах. Обзор проблем реализаций структур данных, рассмотренных в курсе, их обобщение и выведение на уровень поддержки понятий языком программирования. Разграничение уровней абстракции в С и С++ и ее синтаксической поддержки, диктуемое предназначением С и С++. Понятие объекта и класса, их реализация и синтаксис в С++. Сохранение эффективности кода и обратная совместимость как принципы эволюции С в С++. Создание и уничтожение объекта, конструкторы и деструктор. Список инициализации конструктора. Конструктор по умолчанию и конструкторы с единственным параметром, неявность их применения и опасности, с этим связанные. Ключевое слово explicit, причины и история его появления, неидеальность С++ и его разработчиков. Реализация и синтаксис инкапсуляции данных в С++. Функции get* и set*. Динамическая память в C++. Динамическое размещение отдельных объектов и массивов объектов, имеющих деструкторы. Структура блока занятой памяти для массивов объектов, имеющих деструкторы. Применение подходящей формы оператора delete и проблемы, связанные с применением неверной его формы. Реализация класса Стек на С++ через рефакторинг «структуры Стек в стиле С», реализованной ранее. 3 3 4. Абстракция операций в С++. Переопределенные операторы. Пример построения класса «Вектор линейного пространства» или «Натуральная дробь» с переопределенными операторами. Сравнение различных форм записи вычислений над векторами с использованием функций и переопределенных операторов. Задача о симуляции встроенного типа с помощью класса «Целое число». Понятие временного объекта в С++, склонность компилятора создавать временные объекты и способы предотвращения ее проявлений. Рефакторинг функции символьного дифференцирования с применением класса для представления узла дерева и арифметических операторов, переопределенных для него, для сокращения записи правил дифференцирования. 3 3 5. Реализация класса «массив» с проверкой границ и переопределенными операторами. Этапы переопределения оператора «квадратные скобки». Необходимость обрабатывать его вхождения в левых частях выражений присваивания. Понятие Lvalue и Rvalue. Реализация Lvalue через возврат указателя на элемент массива, несинтаксичность этого подхода. Понятие ссылки. Ссылка как «синтаксический сахар» над указателем. Реализация переопределенного оператора «квадратные скобки» с возвратом ссылки на элемент массива. Опасность побочных эффектов в случае неявного применения ссылок. Возврат значения, его оптимизация компилятором (NRVO и RVO), ссылки на Rvalue, реализация семантики перемещения. 3 3 6. Проблема владения для контейнерных классов. Особенности и методики реализации контейнеров как объектов первого класса. Идиома RAII. Понятие конструктора копирования и оператора присваивания, их ключевая роль для контейнерных объектов и ресурсных классов в целом. Стратегии реализации копирования (запрет, поверхностное копирование, глубокое копирование, подсчет ссылок). Реализация класса «строка». Разработка классов с большим количеством функций, опасность нарушения инварианта над данными класса и возникновения проблем работы с данными класса, аналогичных проблемам с глобальными переменными. Построение класса по принципу «ядро и оболочка». Экзотические оптимизации над строками: Small strings optimization для хранения символов строки в поле указателя. 3 3 7. Статический полиморфизм, шаблоны классов и их применение в С++. 3 3 11 № И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса Содержание темы К-во часов Обяз. Доп. Обобщение класса Стек по типу хранимых данных. Шаблонные декларации классов, виды шаблонных параметров, возможность задания их значения по умолчанию, польза и вред этой возможности. Принципы вывода типов при шаблонных конструкций С++. Особенности компиляции шаблонов классов, их влияние на скорость компиляции и размер исполняемого файла. Стадии компиляции шаблонов классов (при декларации и инстанцировании), обработка синтаксических ошибок в них на разных стадиях компиляции. Реализация класса «Массив» через шаблон класса. Спецификация шаблона как средство сохранения оптимальности в частных случаях применения шаблона. Реализация класса «Массив битов». Шаблоны функций. Перегрузка функций. Как надо и как не надо использовать шаблоны. Разбор полезных и бесполезных примеров, связанных с шаблонами. 8. Композиция классов. Наследование без виртуальных функций и приведений типов производных и базовых классов. Синтаксис и семантика открытого и закрытого наследования. Принципы Б. Лисков для верификации отношений наследования. Понятие о ООД. Реализация классов «строка» и «стек» как наследников класса «массив», различных по типу наследования. Агрегирование как частая лучшая альтернатива наследованию с неясной природой и мотивацией. Понятие классов-стратегий. Стратегии динамического и статического хранения данных в классе «массив». Использование классов стратегий как шаблонных параметров. Рефакторинг класса «Массив» с использованием различных стратегий хранения объектов. 3 3 9. Исключения в С++. Задача об обработке исключительных ситуаций. реализация обработки исключительных ситуаций средствами языка С, тяжеловесность синтаксических конструкций. Исключения С++ как синтаксический сахар для решения таких задач. Реализация исключений в С++. try/catch-блоки, оператор throw. Работа оператора throw, свертка стека, гарантии С++ для процесса свертки стека. «Плата» за обработку исключений в случае, если они не происходят. Исключения в конструкторах и деструкторах, поведение системы обработки исключений в случае двойного исключения и отсутствия перехвата исключения, функции unexpected, terminate, set_unexpected и set_terminate. Использование библиотечных классов исключений. 2 3 10. Реализация классов «Список» и «Дерево» с помощью С++. Перечисление элементов контейнера. Понятие итератора. Необходимость в задании пределов итерации контейнера и функциях begin и end. Переопределенные операторы итераторов. Реализация итераторов для списка и дерева. Объявление переменной итератора в случае итерации по шаблону контейнера. Ключевые слова auto и decltype. Стратегия работы с данными как применение алгоритмов над последовательностями, заданными итераторами (по А. Степанову). Основные стандартные контейнеры С++ (библиотека STL). Классы vector, stack, deque, set, map, auto_ptr и другие. Обзор основных функций контейнерных классов STL. Обзор примеров применений контейнерных классов в сравнении с реализациями, использующими собственные классы и реализациями на С. Стандартные алгоритмы С++ (библиотека STL). Обзор основных стандартных алгоритмов STL. Обзор примеров применений алгоритмов в сравнении с реализациями, использующими собственные функции и реализациями на С. Лямбда-функции и их использование. 2 3 11. Ситуации, приводящие к понятию наследования (с виртуальными функциями.) Реализация динамического полиморфизма разными средствами («теги типа», указатели на функции, виртуальные функции). Реализация виртуальных функций через таблицы указателей (vptr/vtable). Применение динамического полиморфизма для встраивания собственного класса в уже работающий программный механизм, приведение типа производного класса к типу базового класса как основной концептуальный синтаксический метод этого встраивания. Реализация собственного класса для хранения наиболее полных данных о возникшем исключении (имя файла, номер строки и т.д.) как наследника стандартных классов 3 3 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 12 К-во часов Обяз. Доп. исключений. Переопределение виртуальной функции what для форматирования сохраненных данных. Работа переопределенной функции совместно с уже существующим механизмом обработки исключений. Частичная обработка исключения и его передача вышележащим функциям, хранение информации об исключении в виде цепочек «исключение - причина исключения - причина причины и т.п.» и польза таких конструкций в конкретизации места и точной причины возникшей ошибки. 12. Понятие иерархии классов, ее роль в обобщенном программировании для систем с высоким повторным использованием кода. Понятие о программном движке. Примеры задач, приводящих к таким системам. Примеры применения систем и иерархий классов: графические движки, физические движки, многоагентные системы. Архитектура «менеджер и подчиненные», ключевая роль виртуальных функций во взаимодействии «менеджера» и «подчиненного». Чисто виртуальные функции. Понятие события и событийной функции. Ситуации, требующие восстановления типов. Механизмы восстановления точного типа объекта, основанные на тегах типов и виртуальных функциях. Оператор dynamic_cast. Динамическое приведение типов. Задача о взаимодействии объектов, зависящего от их типов. Реализация через дважды виртуальные функции (через массив указателей на функции, индексируемый уникальным номером типа объекта) и через паттерн visitor (двойной вызов виртуальных функций). «Ловушки» механизма наследования (путаница с размерами объектов при итерации массивов, множественное наследование). 3 3 13. Понятие о компонентном программировании. Задачи, приводящие к компонентной архитектуре. Реализация ядра системы компонентной архитектуры (основной программы) и подключаемых модулей. Динамически подключаемые библиотеки. Понятие фабрики классов и ее реализация. Реализация фабрики класса через обмен структур с указателями на функции. Сходство таких структур с таблицами виртуальных функций. Реализация через возврат объекта базового класса без данных и лишь с множеством чисто виртуальных функций. Понятие класса-интерфейса. Проблема реализации таблиц виртуальных функций в различных компиляторах. Проблема передачи исключений через границу динамически подключаемой библиотеки. Проблема стандарта API компонентов и его эволюции. Понятие компонентаадаптера. Понятие о шаблонах и анти-шаблонах проектирования. 3 6 14. Разбор проекта мультипроцессорной системы с подключаемыми модулями (полигон для соревнований виртуальных роботов). Архитектура системы, разработка API среды исполнения процессоров и API виртуального процессора. Этапы разработки среды для соревнований виртуальных роботов. Разработка прототипов полигона и базового робота. Построение рабочей версии компонентов системы из прототипов. 3 3 15. Ускорение работы виртуального процессора. Задача бинарной трансляции кода. Just-in-time-компиляторы. Примеры JIT-компиляции Р-кода. Стратегия реализации JIT-компилятора через постепенное замещение интерпретации скомпилированным кодом. Реализация JIT-компиляции безусловного перехода. Реализация арифметических команд, вызовов функций из JITкода и нативных функций С. 3 3 16. Обмен технической информацией. Структура технического сообщения, технология реализации технической презентации (CTP). Примеры реализаций проектов по тематике Intel (С и C++, ООП и ООД «в реальной жизни»). Мини-конференция: сообщения от разных групп студентов, реализовавших тот или иной вариант компонентного проекта. 3 3 Всего за 2 семестр 48 48 Всего за год 93 93 13 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса Годовой образовательный курс лаборатории Intel «Введение в промышленное программирование на языках С, С++ и структуры данных» для студентов 1 курса ФРТК, 2 ч. лекций + 2 ч. семинаров в неделю (сокращенный вариант) И. Р. Дединский, ст. преп. каф. информатики МФТИ Пояснительная записка Курс разработан для студентов 1 курса ФРТК МФТИ, обучающихся в лаборатории Intel. Цель курса – научить студентов современным методам программирования и разработки программных систем на языках С и С++, привить навыки надежного, промышленного программирования, работы в команде, подготовить их для участия в тематических проектах второго курса ILab. Преподавание курса ведется в предположении, что студенты уже знают язык Паскаль или аналогичный процедурный язык. Курс разбит на 2 части: Первая (5-6 занятий) – быстрое практическое введение в С через разбор и решение большого количества небольших задач, заканчивающееся потоковой контрольной работой с автоматической проверкой. Вторая (11-12 занятий) – введение в структуры данных и алгоритмы, практическая часть которой содержит меньшее число задач, но большего объема. Задачи второй части подобраны по большей части таким образом, что в конце курса каждый студент самостоятельно реализует примитивную модель вычислительной системы (стековой виртуальной машины), инструментальные средства низкоуровневой разработки для него (ассемблер и дизассемблер), а также примитивный высокоуровневый транслятор (проект «нано-GCC»). Третья часть курса (6-10 занятий) представляет собой введение в язык С++ в терминах различий С и С++, методом рефакторинга ряда решений на языке С, рассматривавшихся в осеннем семестре. Четвертая часть (6-10 занятий) посвящена технологии применения С++ (ООД, ООП, компонентное программирование) в многомодульном проекте, использующем программный код группы разработчиков в виде динамически подключаемых библиотек. Сложность задач курса легко регулируется их функциональным наполнением (простейший вариант – транслятор формул, используемый для построения графиков элементарных функций). В обучении активно используется менторская система, с помощью которой на практике разбираются темы, приемы и методы, упоминаемые на лекциях. Сдача работ студентами осуществляется через помещение его на серверный репозиторий курса. Основная форма проверки кода менторами – детальный code review с разбором типичных случаев на групповых занятиях. Со второй части курса вводится peer review. Результатом согласованной работы лектора, менторов и студентов должно стать изучение теоретического материала и отработка его на практических задачах, что должно помочь им участвовать в тематических курсах второго года обучения в Лаборатории. Содержание курса (по семестрам) I семестр № 1. Содержание темы Введение в язык С. Краткая история и особенности возникновения языка. Ключевая роль Си в обучении программированию. Высокоуровневые и низкоуровневые языки. Высокоуровневые языки. Переносимость высокоуровневых программ. Проблемы с производительностью и доступом к вычислительным средствам из языков высокого уровня.. Си как язык промежуточного уровня, задуманный и построенный как компромисс между низким и высоким уровнем. Командная разработка проектов. Системы контроля версий, репозитории и работа с ними. Системы контроля версий SVN Code review в системе контроля версий. Типичный рабочий процесс при работе с системой контроля версий SVN и репозиторием Google Code в Linux и Windows. Рабо- К-во часов Лек. Сем. 2 2 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 14 К-во часов Лек. Сем. чий процесс для code review в ILab. Тестирование программ. Тестирование с помощью специальных программ (серверов). 2. Трансляция и построение программы. Понятие программного проекта. Трансляция файла с исходным текстом. Соотнесение исходного текста и исполняемого кода программы. Понятие раздельной компиляции. Понятие о включаемых заголовочных файлах, информация, содержащаяся в них, ее использование в процессе трансляции. Понятие библиотеки как объединения объектных файлов. Введение в язык Си. Структура и синтаксис простейшей программы на языке Си. Раздел включаемых заголовочных файлов, главная программа. Объявление переменных, типы данных (на примере int и double). Ввод и вывод информации, функции printf и scanf. Арифметические выражения, операторы сложения, вычитания, умножения и деления, оператор присваивания. Функция вычисления квадратного корня. Условный оператор. 2 2 3. Форматирование кода, его важность для реализации надежности и командности, его виды, понятие корпоративного стиля форматирования. Понятие определения функции, ее заголовка и тела. Формальные параметры функции квадратного уравнения (коэффициенты), синтаксис их объявления. Понятие и синтаксис вызова функции. Понятие прототипа функции, синтаксис. Модульный принцип построения программ. Передача данных через оператор return. Анализ возвращаемого значения в main, оператор switch, его синтаксис. Проблема передачи информации о бесконечном количестве корней. Использование «магических чисел» и именованных констант для обозначения бесконечного количества корней. Синтаксис определения именованной константы. Указатели, передача данных через параметр-указатель. Макрос assert, его применение для проверки указательных параметров, информативность для программиста при отладке. 2 2 4. Комментирование кода. Блочные комментарии файла и функции. Препроцессор языка C. Директивы препроцессора. Директива include для стандартных заголовочных файлов. Директива define. Использование директивы для задания констант, ее отличия от конструкции с const. Директива define с параметрами. Особенности и побочные эффекты в случае макроопределения с параметрами, ее отличие от функций. Классические примеры построения макроопределения с параметрами с демонстрацией побочных эффектов и защитой параметров скобками. Продолжение макроопределения на следующие строки с помощью символа обратной косой черты. Ошибки применения define: использование аргументов с побочным эффектом (инкремент/декремент переменных, вызовы функций, работающих с потоками и т.п.)Стандартные макроопределения __FILE__, __LINE__, __DATE__, __TIME__. Разбор механизма влияния NDEBUG на assert, условная компиляция. Директивы #ifdef, #ifndef, #if (и синтаксис допустимых в нем конструкций), #else, #elif, #endif. 2 2 5. Массивы в языке Си. Использование массивов для хранения серий данных. Объявление и инициализация массива. Ограничения массивов в Си (нумерация, единство типа данных, ограниченный размер). Хранение массивов в оперативной памяти. Адресация к массиву. Имя массива как адрес (указатель) его начального элемента. Типичные ошибки при работе с массивами (выход за границы массива).Проверка допустимости индексации с помощью assert. Решение проблемы волатильности с помощью модификатора const. 2 2 6. Динамическая память в языке Си. Понятие «свободной памяти». Функции работы с динамической памятью. Время жизни блока динамической памяти. Динамическая память как ресурс, работа с исчерпанием памяти, реализация стратегий гарантированного освобождения. Пример структуры блока динамической памяти. Последствия выхода за границы блока, двойного освобождения блока, переполнения буфера, находящегося в динамической памяти. Реаллокация блоков динамической памяти, проблема пересчета указателей в случае изменения адреса блока. Указательная арифметика. Операции с указателями в языке См. Формула вычисления адреса для доступа к элементу массива. Использование указательной арифметики для потоковых вычислений на массивах, понятие «текущего элемента». 2 2 15 № И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса Содержание темы К-во часов Лек. Сем. 7. Многомерные массивы, их объявление, инициализация, адресация в них. Проблема передачи многомерного массива в функцию. Вычисление адреса элемента в многомерном массиве. Необходимость передавать размеры многомерного массива в функцию. Самостоятельное вычисление смещения относительно начала массива и адреса нужного элемента массива, преимущества и недостатки такого подхода. Реализация многомерных массивов в динамической памяти, доступ к таким массивам. Массивы указателей. Синтаксис объявления и использования массивов указателей. Трактовка операции индексации в случае массивов указателей. Реализация многомерных массивов через массивы указателей, Решение вопроса о хранении массива с «рваным правым краем» (неодинаковым размером строк). Использование разных блоков для хранения разных строк массива, возможность реаллокации для изменения длин строк. 2 2 8. Строки. Реализация строк в языке Си, «смысловая» и «свободная» зоны строки, Нулевой символ. Понятие пустой строки. Задачи о копировании и сравнении строк, задача о сжатии пробелов в строке «на месте». Концепция «текущего символа». Проблемы «маляра Шлемиля (Шлемиэля)», их характерные проявления и устранение. Возможности строковой библиотеки языка Си. Массивы строк. Задача о сортировке строк, обобщение алгоритмов сравнения строк. Указатели на функции. Использование указателей на функции для построения универсальной функции сортировки строк. Библиотечная функция qsort и работа с ней. Работа с файлами. Функции открытия и закрытия файла. Текстовые файлы, посимвольное и построчное считывание. Состояние «конец файла», константа EOF. Опасность переполнения буфера при чтении. Форматированный текстовый ввод и вывод, опасности, с ним связанные. Символы преобразования данных и форматирования. Бинарные файлы. Перемещение по файлу. 2 2 9. Структуры. Операции доступа к структурам. Построение структур, размер структуры. Передача структур в функцию, способы ее ускорения (через указатель) и защиты доступа (через указатель на константные данные). Реализация «методов класса» средствами языка Си. 2 2 10. Понятие абстрактных структур данных.Структура данных «стек».Функции для работы со стеком. Функции конструирования, уничтожения, верификации и технической распечатки (дампа). Пример реализации функции дампа. Использование стека. Задача о вычислении выражений. Вычисление выражений, заданных обратной польской записью. Понятие стекового вычислителя (процессора). Реализация структуры стекового вычислителя и связанных с ней функций. Реализация арифметических команд для стекового вычислителя. Примеры работы стекового вычислителя. Интерактивный режим работы программы вычисления выражений. Задача о построении таблицы значений функции или ее графика. Понятие регистра вычислителя (процессора), введение регистра абсциссы (АХ) в стековый вычислитель. Функция на языке Си для загрузки значения абсциссы в вычислитель (mov_ax). 2 2 11. Проблема скорости работы стекового вычислителя при исполнении стереотипного кода для каждого заданного значения абсциссы. Реализация программирования вычислителя с помощью текстового файла с последовательностью команд в обратной польской записи. Анализ скорости работы такой программы, определение «узких мест» для повышения эффективности. Реализация дизассемблера для целей отладки. Хранение программы в Р-кодах в массиве команд, преимущества такого подхода перед постоянным чтением их из файла. Отделение фазы загрузки программы из файла с Р-кодом в массив команд от фазы исполнения программы. Реализация эффективного программного комплекса стекового вычислителя для построения графиков или таблиц значений функций и системных утилит для него. 2 2 12. Расширение круга задач для стекового вычислителя. Обобщение постановки задачи для построения таблиц значений (графиков) функций с необходимостью единственного исполнения программы и организации перебора значений аргумента в Р-программе. Пример возможной Р-программы для построения таблиц значений (графика) функции. Необходимость команд текстового или графического вывода данных (out), команд условного и без- 2 2 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 16 К-во часов Лек. Сем. условного переходов (j*, jmp) для организации цикла в Р-программе. Проблема аргумента в командах переходов. Реализация команд переходов с помощью вручную рассчитанных адресов переходов, недостатки такого подхода. Задача автоматического расчета адресов переходов. Понятие меток как синонимов адресов. Реализация вызова функций в стековом вычислителе. Сравнение работы команд вызова функции и безусловного перехода. Понятие возврата из функции. Реализация команд вызова функции с аргументом в виде метки и возврата из функции с помощью отдельного стека для хранения адресов возврата. Выполнение задач на стековом вычислителе в виде написания самостоятельно разработанных Р-программ (решение квадратных уравнений с разбором всех частных случаев, выдачей количества и величин их корней, вычисления факториала чисел и чисел Фибоначчи итеративным и рекурсивным способами). Расширение количества регистров (добавление регистров bx, cx, dx), системы команд (добавления команды ввода с клавиатуры in). 13. Структура данных «список». Использование списков. Односвязные и двусвязные списки. Неэффективность операции индексации для списков (еще один пример «алгоритма маляра Шлемиля»).Проверка валидности списка, Структура данных «хеш-таблица». Задачи, приводящие к хеш-таблицам. Хеш-функции, их примеры (от простейших и бесполезных к реальным) и свойства, качество хеширования. Характерные размеры хеш-таблиц. Использование хеш-таблиц. Качественное сравнение качества хеширования с помощью гистограммы заполнения хеш-таблицы. 2 2 14. Структура данных «дерево». Примеры различного использование деревьев. Деревья поиска. Перечисление узлов дерева, виды обходов дерева. Верификация деревьев. Дамп деревьев. Задачи, использующие деревья. Структура арифметических выражений. Инфиксная форма записи выражений, ее соответствие порядку действий и дереву вычислений. Задача о грамматическом разборе выражений. Необходимость задания структуры выражений. Понятие языка и грамматики. Способы построения дерева разбора. Алгоритм распознавания языка методом рекурсивного спуска. Различные обходы деревьев выражений, восстановление линейной инфиксной записи и генерация различных видов польских записей (префиксной, постфиксной). Транслятор инфиксных выражений в ассемблер стекового процессора. Лексический анализ как предварительная фаза перед синтаксическим, его роль в повышении эффективности трансляции и ее упрощении. Понятие лексемы, ее реализация. Рефакторинг транслятора с применением лексического анализа. 2 2 15. Архитектура nGCC. Front-end, middle-end и back-end. Достоинства модульного принципа и общего внутреннего формата. Разработка общего внутригруппового стандарта промежуточного файла с AST, поддерживающего дополнительные данные (имена переменных и т.п.). Рефакторинг транслятора инфиксных выражений с использованием архитектуры nGCC. Реализация программы для запуска частей транслятора (драйвера). Реализация обратного преобразования (из AST в код модельного высокоуровневого языка). 2 2 30 30 Всего за 1 семестр II семестр № 1. Содержание темы Преобразование структуры выражения. Выражения, эквивалентные в смысле вычисления. Примеры преобразования выражений. Символьное дифференцирование. Реализация символьного дифференцирования как обход дерева выражения с построением нового дерева. Реализация функции дифференцирования и ее сходство с таблицей производных элементарных функций и правил дифференцирования. Дифференцирование сложной функции. Реализация «оператора символьного дифференцирования». К-во часов Лек. Сем. 2 2 17 № И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса Содержание темы К-во часов Лек. Сем. 2. Оптимизации над выражением. Недостатки деревьев выражений, построенных при символьном дифференцировании. Простейшие оптимизации (удаление ненужных операций с нулем и единицей, свертка констант). Понятие о действиях (операторах), зависимых по данным. Преобразования последовательностей операторов, независимых по данным. Задача о «перемешивании» кода программы с сохранением корректности алгоритма. 2 2 3. Понятие объекта и класса, их реализация и синтаксис в С++. Создание и уничтожение объекта, конструкторы и деструктор. Список инициализации конструктора. Конструктор по умолчанию и конструкторы с единственным параметром, неявность их применения и опасности, с этим связанные. Ключевое слово explicit. Реализация и синтаксис инкапсуляции данных в С++. Функции get* и set*. Динамическая память в C++. Динамическое размещение отдельных объектов и массивов объектов, имеющих деструкторы. Применение подходящей формы оператора delete и проблемы, связанные с применением неверной его формы. Реализация класса Стек на С++ через рефакторинг «структуры Стек в стиле С», реализованной ранее. 2 2 4. Абстракция операций в С++. Переопределенные операторы. Пример построения класса «Вектор линейного пространства». Рефакторинг функции символьного дифференцирования с применением класса для представления узла дерева и арифметических операторов, переопределенных для него, для сокращения записи правил дифференцирования. 2 2 5. Реализация класса «массив» с проверкой границ и переопределенными операторами. Этапы переопределения оператора «квадратные скобки». Необходимость обрабатывать его вхождения в левых частях выражений присваивания. Понятие ссылки. Ссылка как «синтаксический сахар» над указателем. Реализация переопределенного оператора «квадратные скобки» с возвратом ссылки на элемент массива. Опасность побочных эффектов в случае неявного применения ссылок. 2 2 6. Проблема владения для контейнерных классов. Особенности и методики реализации контейнеров как объектов первого класса. Понятие конструктора копирования и оператора присваивания, их ключевая роль для контейнерных объектов и ресурсных классов в целом. Стратегии реализации копирования (запрет, поверхностное копирование, глубокое копирование, подсчет ссылок). Реализация класса «строка». 2 2 7. Статический полиморфизм, шаблоны классов и их применение в С++. Обобщение класса Стек по типу хранимых данных. Шаблонные декларации классов, виды шаблонных параметров. Принципы вывода типов при шаблонных конструкций С++. Стадии компиляции шаблонов классов (при декларации и инстанцировании), обработка синтаксических ошибок в них на разных стадиях компиляции. Реализация класса «Массив» через шаблон класса. Спецификация шаблона как средство сохранения оптимальности в частных случаях применения шаблона. Шаблоны функций. Перегрузка функций. 2 2 8. Композиция классов. Наследование без виртуальных функций и приведений типов производных и базовых классов. Синтаксис и семантика открытого и закрытого наследования. Принципы Б. Лисков для верификации отношений наследования. Агрегирование как частая лучшая альтернатива наследованию с неясной природой и мотивацией. 2 2 9. Исключения в С++. Задача об обработке исключительных ситуаций. реализация обработки исключительных ситуаций средствами языка С, тяжеловесность синтаксических конструкций. Исключения С++ как синтаксический сахар для решения таких задач. Реализация исключений в С++. try/catchблоки, оператор throw. Работа оператора throw, свертка стека, гарантии С++ для процесса свертки стека. Исключения в конструкторах и деструкторах, поведение системы обработки исключений в случае двойного исключения и отсутствия перехвата исключения. Использование библиотечных классов исключений. 2 2 10. Реализация классов «Список» и «Дерево» с помощью С++. Перечисление элементов контейнера. Понятие итератора. Необходимость в задании преде- 2 2 И.Р. Дединский. Годовой образовательный курс лаборатории Intel для студентов 1 курса № Содержание темы 18 К-во часов Лек. Сем. лов итерации контейнера и функциях begin и end. Переопределенные операторы итераторов. Реализация итераторов для списка и дерева. Объявление переменной итератора в случае итерации по шаблону контейнера. Основные стандартные контейнеры С++ (библиотека STL). Классы vector, stack, deque, set, map, auto_ptr и другие. Обзор основных функций контейнерных классов STL. Обзор примеров применений контейнерных классов в сравнении с реализациями, использующими собственные классы и реализациями на С. Стандартные алгоритмы С++ (библиотека STL). Обзор основных стандартных алгоритмов STL. Обзор примеров применений алгоритмов в сравнении с реализациями, использующими собственные функции и реализациями на С. 11. Ситуации, приводящие к понятию наследования (с виртуальными функциями.) Реализация динамического полиморфизма разными средствами («теги типа», указатели на функции, виртуальные функции). Применение динамического полиморфизма для встраивания собственного класса в уже работающий программный механизм, приведение типа производного класса к типу базового класса как основной концептуальный синтаксический метод этого встраивания. Реализация собственного класса для хранения наиболее полных данных о возникшем исключении (имя файла, номер строки и т.д.) как наследника стандартных классов исключений. Переопределение виртуальной функции what для форматирования сохраненных данных. Работа переопределенной функции совместно с уже существующим механизмом обработки исключений. 2 2 12. Понятие иерархии классов, ее роль в обобщенном программировании для систем с высоким повторным использованием кода. Понятие о программном движке. Примеры задач, приводящих к таким системам. Архитектура «менеджер и подчиненные», ключевая роль виртуальных функций во взаимодействии «менеджера» и «подчиненного». Чисто виртуальные функции. Понятие события и событийной функции. Ситуации, требующие восстановления типов. Механизмы восстановления точного типа объекта, основанные на тегах типов и виртуальных функциях. Оператор dynamic_cast. Динамическое приведение типов. Задача о взаимодействии объектов, зависящего от их типов. 2 2 13. Понятие о компонентном программировании. Задачи, приводящие к компонентной архитектуре. Реализация ядра системы компонентной архитектуры (основной программы) и подключаемых модулей. Динамически подключаемые библиотеки. 2 2 14. Разбор проекта мультипроцессорной системы с подключаемыми модулями (полигон для соревнований виртуальных роботов). Архитектура системы, разработка API среды исполнения процессоров и API виртуального процессора. 2 2 15. Ускорение работы виртуального процессора. Задача бинарной трансляции кода. Just-in-time-компиляторы. 2 2 Всего за 2 семестр 30 30 Всего за год 60 60