Dyzzet|
C++ Data Science Алгоритмы Темы · Блог · YouTube · Telegram
Обзоры книг по C++
Советы и задачи по языку

Курт Гантерот. Оптимизация программ на C++. Проверенные методы для повышения производительности

400 страниц. Альфа-книга, 2017

Kurt Guntheroth. Optimized C++. Proven Techniques for Heightened Performance. O’Reilly, 2016

«Приветствую вас! Меня зовут Курт, и я кодоголик».

Подход обстоятельный. В начале книги описывается закон Амдала — улучшение времени выполнения \(S_T\):

\(S_T = \dfrac{1}{(1-P)+\frac{P}{S_p}}\),

где \(P\) — доля оптимизированного общего времени выполнения, \(S_p\) — показатель улучшения в оптимизированной части \(P\). Например, если некоторая функция выполнялась 80 процентов всего времени работы программы, а улучшенная её версия стала работать на 30 процентов быстрее, иначе говоря, \(P=0{,}8\), \(S_p=1{,}3\), то \(S_T\approx 1{,}22\).

Глава «Оптимизация, влияющая на поведение компьютера» — очерк того, с какими абстракциями аппаратного и программного обеспечения приходится иметь дело. Большая часть тем — это последовательное улучшение некоторого базового примера.

Глава «Измерение производительности» рассказывает о физике процесса, о стандартах (как они определяют прецизионность, истинность и точность — accuracy, trueness, precision). С историческими справками и примерами. И так вплоть до класса-секундомера.

Глава «Оптимизация использования строк» описывает проблемыб из-за которых проседает производительность, анализирует их и предлагает пути решения. Здесь предлагаются варианты работы со стандартными классами, C-строками, std::string_view и разными сторонними библиотеками (folly::fbstring, Folly и другими).

В главе «Оптимизация алгоритмов» обсуждаются некоторые детали анализа сложности, частые заблуждения о сложности известных алгоритмов, а также шаблоны оптимизации (предварительные вычисления, отложенные вычисления и другие).

Сложно коротко описать, о чём глава «Оптимизация переменных в динамической памяти», название не отражает всего спектра тем, которые там излагаются. Может быть, самая важная глава в книге.

Глава «Оптимизация инструкций» рассказывает о понятии инварианта, удалении скрытых вызовов и кода из функций, а ещё о том, что в какой-то момент нужно и остановиться:

«Мудрые разработчики, занимающиеся оптимизацией кода, знают, что лучше не ослаблять дизайн без уважительной причины и не гоняться за каждой виртуальной функцией программы со скальпелем в руке».

Здесь же автор горячо рекламирует книгу Генри С. Уоррена-мл. «Алгоритмические трюки для программистов» (Henry S. Warren, Jr. Hacker’s Delights).

Глава «Использование лучших библиотек» немного рассказывает о стандартной библиотеке, о заблуждениях, связанных с ней, о затем — о том, каким базовым принципам должен следовать разработчик хорошей библиотеки.

«Оптимизация сортировки и поиска», «Оптимизация структур данных», «Оптимизация ввода-вывода», «Оптимизация параллельности» — главы о стандартной библиотеке и других библиотеках.

Глава «Оптимизация управления памятью» рассказывает в том числе про размещающий оператор new, функции для работы с памятью в стиле C и разного рода библиотеки.

Книга обстоятельная, но доходчивая. С редкими врезками с историями из жизни. Ещё бы «каркасы» в переводе были «фреймворками»...

Стефан К. Дьюхэрст. Скользкие места C++. Как избежать проблем при проектировании и компиляции ваших программ

264 страницы. ДМК, 2006

Stephen C. Dewhurst. C++ Gotchas. Avoiding Common Problems in Coding and Design. Addison-Wesley, 2003

99 советов, сгруппированных по темам. Содержание: основы, синтаксис, препроцессор, преобразования, инициализация, управление памятью и ресурсами, полиморфизм, проектирование классов, проектирование иерархий.

Все плохие примеры кода написаны на сером фоне, хорошие — на белом (по большей части).

Примерно половина книги — довольно простые, но хитрые примеры.

h.*value = 1.85
*(double *)((char *)&h+(value-1)) = 1.85

Стивен С. Дьюхерст. C++. Священные знания

240 страниц. Символ-Плюс, 2007

Stephen C. Dewhurst. C++ Common Knowledge. Addison-Wesley, 2005

Оригинальное название — отсылка к колонке Стивена Дьюхерста Common Knowledge в журнале C++ Report (главредом был Герб Саттер).

Из отзыва Стенли Липпмана мы узнаём: «Стив научил меня C++. Это было в далёком 1982 или 1983. Я думаю, он тогда только вернулся с последипломной практики, которую проходил вместе с Бьерном Страуструпом в научно-исследовательском центре Bell Laboratories. Стив — один из невоспетых героев, стоящих у истоков».

63 темы без группировки. Это и «старые» вещи вроде указателей на указатели, а также тонкости реализации ООП в C++, шаблоны, STL, некоторые шаблоны проектирования.

Книги Скотта Мейерса

Скотт Мейерс. Эффективное использование STL, 2002

Далее идут четыре перевода книг Скотта Мейерса. Для удобства представлено содержание каждой из них. Отдельные части могут дублироваться, но в целом это разные книги. В приложении к переводу книги 2006 года есть таблицы соответствия между изданиями.

В предисловии к третьему изданию говорится, что первый вариант книги «Эффективное использование C++» появился ещё в 1991 году (перевода этого издания, по-видимому, нет). Второе издание 1997 года почти полностью его повторяло.

Скотт Мейерс. Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов

304 страницы. ДМК Пресс, 2000

Scott Meyers. More Effective C++. 35 New Ways to Improve Your Programs and Designs. Addison-Wesley, 1996

Книга хоть и старая, но актуальности не теряет. Практически чек-лист для тех, кто изучили основы и намереваются разобраться глубже.

  • Основы
    1. Различайте указатели и ссылки
    2. Предпочитайте приведение типов в стиле C++
    3. Никогда не используйте полиморфизм в массивах
    4. Избегайте неоправданных конструторов по умолчанию
  • Операторы
    1. Опасайтесь определяемых пользователем функций преобразования типа
    2. Различайте префиксную и постфиксную формы операторов инкремента и декремента
    3. Никогда не перегружайте операторы &&, || и ,
    4. Различайте значение операторов new и delete
  • Исключения
    1. Чтобы избежать утечки ресурсов, используйте деструкторы
    2. Не допускайте утечки ресурсов в конструкторах
    3. Не распространяйте обработку исключений за пределы деструктора
    4. Отличайте генерацию исключения от передачи параметра или вызова виртуальной функции
    5. Перехватывайте исключения, передаваемые по ссылке
    6. Разумно используйте спецификации исключений
    7. Оценивайте затраты на обработку исключений
  • Эффективность
    1. Не забывайте о правиле «80/20»
    2. Используйте отложенные вычисления
    3. Снижайте затраты на ожидаемые вычисления
    4. Изучите причины возникновения временных объектов
    5. Облегчайте оптимизацию возвращаемого значения
    6. Используйте перегрузку, чтобы избежать неявного преобразования типов
    7. По возможности применяйте оператор присваивания вместо отдельного оператора
    8. Используйте разные библиотеки
    9. Учитывайте затраты, связанные с виртуальными функциями, множественным наследованием, виртуальными базовыми функциями и RTTI
  • Приёмы
    1. Делайте виртуальными конструкторы и функции, не являющиеся членами класса
    2. Ограничивайте число объектов в классе
    3. В зависимости от ситуации требуйте или запрещайте размещать объекты в куче
    4. Используйте интеллектуальные указатели
    5. Используйте подсчёт ссылок
    6. Применяйте proxy-классы
    7. Создавайте функции, виртуальные по отношению более чем к одному объекту
  • Разное
    1. Программируйте, заглядывая в будущее
    2. Делайте нетерминальные классы абстрактными
    3. Умейте использовать в одной программе C и C++
    4. Ознакомьтесь со стандартом языка

Приложения: список рекомендуемой литературы, реализация шаблона auto_ptr (объявлен устаревшим в C++11, удалён в C++17).

Для примера посмотрим на 5-й совет: «Опасайтесь определяемых пользователем функций преобразования типа».

class Rational {
public:
   ...
   operator double() const;     // Преобразует Rational
                                // к типу double.
};

Объект класса Rational теперь можно явно или неявно приводить к типу double.

Rational r(1, 2);               // Значение r равно 1/2.
double d = 0.5 * r;             // Преобразует r к типу
                                // double, а затем выполняет
                                // операцию умножения.

Если для этого типа не перегрузить оператор <<, то cout << r выведет 0.5, а например, не 1/2.

А вы знали, почему тип возвращаемого значения у перегруженного оператора постинкремента operator++(int) следует делать константным? Чтобы вызов i++++ не работал (как он не работает для типа int), тем более, что всё равно i будет увеличиваться единожды. А почему не стоит обрабатывать исключения по указателю? Как убедиться, что объект создан в куче? А запретить создавать объекты класса в куче? Вопросы вполне прикладные.

Книга написана простым языком, местами с юмором. Например, автор сравнивает ленивые вычисления с тем, как пятилетний ребёнок до конца откладывает уборку в комнате, пока не услышит, как родители пойдут смотреть на наведённый порядок.

Некоторые вещи звучат странно, например, совет делать конструкторы и функции, не являющиеся членами класса, виртуальными. Это невозможно, так просто называется некая техника.

Книгу писали давно, поэтому будут такие пассажи: «Функция operator new[] добавлена в стандарт языка сравнительно недавно». Спецификация исключений (правило 14), умные указатели (правило 28) — это уже всё устарело.

Перевод не идеальный. Так, «a const object» стал «объектом с атрибутом const», который вообще-то квалификатор. В «отслоённых объектах» можно не сразу угадать sliced objects, то есть объекты, которые подверглись срезке (slicing). А name mangling назван «коррекцией имён». Есть опечатки, некоторые — смешные («статистические объекты»).

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

Амперсанд легко прекратился в s.

void uppercasify(string& str);              // changes all chars in
                                            // str to upper case
void uppercasify(strings str);              // Переводит все символы
                                            // в str в верхний регистр

Скотт Мейерс. Эффективное использование C++. 50 рекомендаций по улучшению ваших программ и проектов

240 страниц. ДМК Пресс, Питер 2006

Scott Meyers. More Effective C++. 35 New Ways to Improve Your Programs and Designs. Addison-Wesley, 1998

По какой-то причине «35 новых способов улучшить ваши программы и проекты» в переводе стали 50-ю.

  • Переход от C к C++
    1. Предпочитайте const и inline использованию #define
    2. Предпочитайте <iostream> использованию <stdio.h>
    3. Предпочитайте new и delete использованию malloc и free
    4. Предпочитайте комментарии в стиле C++
  • Управление памятью
    1. Используйте одинаковые формы new и delete
    2. Используйте delete в деструкторах для указателей членов
    3. Будьте готовы к нехватке памяти
    4. При написании операторов new и delete придерживайтесь ряда простых правил
    5. Старайтесь не скрывать «нормальную» форму new
    6. Если вы написали оператор new, напишите и оператор delete
  • Конструкторы, деструкторы и операторы присваивания
    1. Для классов с динамическим выделением памяти объявляйте копирующий конструктор и оператор присваивания
    2. Предпочитайте инициализацию присваиванию в конструкторах
    3. Перечисляйте члены в списке инициализации в порядке их объявления
    4. Убедитесь, что базовые классы имеют виртуальные деструкторы
    5. operator= должен возвращать ссылку на *this
    6. В operator= присваивайте значения всем элементам данных
    7. В operator= осуществляйте проверку на присваивание самому себе
  • Классы и функции: проектирование и объявление
    1. Стремитесь к таким интерфейсам классов, которые будут полными и минимальными
    2. Проводите различие между функциями-членами, функциями, не являющимися членами класса, и дружественными функциями
    3. Избегайте данных в открытом интерфейсе
    4. Везде, где только можно, используйте const
    5. Предпочитайте передачу параметров по ссылке передаче по значению
    6. Не пытайтесь вернуть ссылку, когда вы должны вернуть объект
    7. Тщательно обдумывайте выбор между перегрузкой функции и аргументами по умолчанию
    8. Избегайте перегрузки по указателю и численному типу
    9. Примите меры предосторожности против потенциальной неоднозначности
    10. Явно запрещайте использование нежелательных функций-членов, создаваемых компилятором по молчанию
    11. Расчленяйте глобальное пространство имён
  • Классы и функции: реализация
    1. Избегайте возврата «дескрипторов» внутренних данных
    2. Не используйте функции-члены, возвращающие неконстантные указатели или ссылки на члены класса с более ограниченным доступом
    3. Никогда не возвращайте ссылку на локальный объект или разыменованный указатель, инициализированный внутри функции посредством new
    4. Откладывайте определение переменных до последнего момента
    5. Тщательно обдумывайте использование встраиваемых функций
    6. Уменьшайте зависимости файлов при компиляции
  • Наследование и объектно-ориентированное проектирование
    1. Используйте открытое наследование для моделирования отношения «есть разновидность» (is-a)
    2. Различайте наследование интерфейса и наследование реализации
    3. Никогда не переопределяйте наследуемые невиртуальные функции
    4. Никогда не переопределяйте наследуемое значение аргумента по умолчанию
    5. Избегайте приведения типов вниз по иерархии наследования
    6. Моделируйте отношения «содержит» (has-a) и реализуется посредством (uses-a) с помощью вложения (агрегации)
    7. Различайте наследование и шаблоны
    8. Продумывайте подход к использованию закрытого наследования
    9. Продумывайте подход к использованию множественного наследования
    10. Говорите то, что думаете, понимайте то, что говорите
  • Другие принципы
    1. Необходимо знать, какие функции неявно создаёт и вызывает C++
    2. Предпочитайте ошибки во время компиляции ошибкам во время выполнения
    3. Обеспечьте инициализацию нелокальных статических объектов до их использования
    4. Уделяйте внимание предупреждениям компилятора
    5. Ознакомьтесь со стандартной библиотекой
    6. Старайтесь понимать цели C++

Сперва — о паре устаревших вещей. Трудно сказать, может, в 1998 году предостережение из четвёртого совета «Предпочитайте комментарии в стиле C++» (в целом верного и хорошего) имело смысл, но сейчас трудно об этом судить:

«Старые препроцессоры, легко обрабатывающие C, „не знают“, как работать с комментариями в стиле C++, потому иногда использование таковых может вызывать неожиданные эффекты».

#define LIGHT_SPEED 3e8        // м/сек (в вакууме).

Совет 27 «Явно запрещайте использование нежелательных функций-членов, создаваемых компилятором по умолчанию» предлагает делать такие члены закрытыми, а стандарт C++11 для этого предлагает = delete.

Хоть книга и ровесница первого международного стандарта C++98, в остальном она абсолютно полезна и актуальна. Там есть всё или почти всё, что нужно знать тем, кто изучают язык и хотят глубже погрузиться в практику его использования.

Скотт Мейерс. Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ

300 страниц. ДМК Пресс, 2006

Scott Meyers. Effective C++. 55 Specific Ways to Improve Your Programs and Designs. Addison-Wesley, 2005

  • Приучайтесь к C++
    1. Относитесь к C++ как к конгломерату языков
    2. Предпочитайте const, enum и inline использованию #define
    3. Везде, где только можно, используйте const
    4. Прежде чем использовать объекты, убедитесь, что они инициализированы
  • Конструкторы, деструкторы и операторы присваивания
    1. Какие функции C++ создаёт и вызывает молча
    2. Явно запрещайте компилятору генерировать функции, которые вам не нужны
    3. Объявляйте деструкторы виртуальными в полиморфном базовом классе
    4. Не позволяйте исключениям покидать деструкторы
    5. Никогда не вызывайте виртуальные функции в конструкторе или деструкторе
    6. Операторы присваивания должны возвращать ссылку на *this
    7. В operator= осуществляйте проверку на присваивание самому себе
    8. Копируйте все части объекта
  • Управление ресурсами
    1. Используйте объекты для управления ресурсами
    2. Тщательно продумывайте поведение при копировании классов, управляющих ресурсами
    3. Предоставляйте доступ к самим ресурсам из управляющих ими классов
    4. Используйте одинаковые формы new и delete
    5. Помещение в «интеллектуальный» указатель объекта, выделенного с помощью new, лучше располагать в отдельном предложении
  • Проектирование программ и объявления
    1. Проектируйте интерфейсы так, что их легко было использовать правильно и трудно — неправильно
    2. Рассматривайте проектирование класса как проектирование типа
    3. Предпочитайте передачу по ссылке на const передаче по значению
    4. Не пытайтесь вернуть ссылку, когда должны вернуть объект
    5. Объявляйте данные-члены закрытыми
    6. Предпочитайте функциям-членам функции, не являющиеся ни членами, ни друзьями класса
    7. Объявляйте функции, не являющиеся членами, когда преобразование типов должно быть применимо ко всем параметрам
    8. Подумайте о поддержке функции swap, не возбуждающей исключений
  • Реализация
    1. Откладывайте определение переменных насколько возможно
    2. Не злоупотребляйте приведением типов
    3. Избегайте возвращения «дескрипторов» внутренних данных
    4. Стремитесь, чтобы программа была безопасна относительно исключений
    5. Тщательно обдумывайте использование встроенных функций
    6. Уменьшайте зависимости файлов при компиляции
  • Наследование и объектно-ориентированное проектирование
    1. Используйте открытое наследование для моделирования отношения «является» (is-a)
    2. Не скрывайте унаследованные имена
    3. Различайте наследование интерфейса и наследование реализации
    4. Рассмотрите альтернативы виртуальным функциям (шаблоны проектирования «Шаблонный метод», «Стратегия» — последний с помощью указателей на функции и посредством класса tr::function)
    5. Никогда не переопределяйте наследуемые невиртуальные функции
    6. Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию
    7. Моделируйте отношение «содержит» (has-a) или «реализуется посредством» (uses-a) с помощью композиции
    8. Продумывайте подход к использованию закрытого наследования
    9. Продумывайте подход к использованию множественного наследования
  • Шаблоны и обобщённое программирование
    1. Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции
    2. Усвойте оба значения ключевого слова typename
    3. Необходимо знать, как обращаться к именам в шаблонных базовых классах
    4. Размещайте независимый от параметров код вне шаблонов
    5. Разрабатывайте шаблоны функций-членов так, чтобы они принимали «все совместимые типы»
    6. Определите внутри шаблонов функции, не являющиеся членами, когда желательны преобразования типа
    7. Используйте классы-характеристики для предоставления информации о типах
    8. Изучите метапрограммирование шаблонов
  • Настройка new и delete
    1. Разберитесь в поведении обработчика new
    2. Когда имеет смысл заменять new и delete
    3. Придерживайтесь принятых соглашений при написании new и delete
    4. Если вы написали оператор new с размещением, напишите и соответствующий оператор delete
  • Разное
    1. Обращайте внимание на предупреждения компилятора
    2. Ознакомьтесь со стандартной библиотекой, включая TR1
    3. Познакомьтесь с Boost

Приложения: «За пределами „Эффективного использования C++“», соответствие правил во втором и третьем изданиях.

Книгу можно назвать переработкой прошлой. Видно, что она готовилась в преддверии стандарта C++11 (этот стандарт имел рабочее название C++0x, потому что его планировали выпустить ещё до 2010 года): в книге используются какие-то вещи из TR1 (Technical Report 1) — новые шаблоны классов и функций в пространстве имён std::tr1, которые стали частью стандартной библиотеки C++11. Например std::tr1::shared_ptr, который позже стал std::shared_ptr.

Вот пример совета. Совет 32 «Используйте открытое наследование для моделирования отношения „является“».

class Penguin : public Bird {
public:
   virtual void fly() {error("Попытка заставить пингвина летать!");}
   ...
};

«Важно понимать, что это здесь имеется в виду не совсем то, что вам могло показаться. Мы не говорим: „Пингвины не могут летать“, а лишь сообщаем: „Пингвины могут летать, но с их стороны было бы ошибкой это делать“. В чём разница? Во времени обнаружения ошибки».

Как это стало привычным, что-то устарело (std::auto_ptr), где-то есть «особенности» перевода (deleter — «чистильщик»).

Скотт Мейерс. Эффективный и современный C++. 42 рекомендации по использованию C++11 и C++14

304 страницы. «Вильямс», 2016

Scott Meyers. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14. O’Reilly, 2015

Эта книга охватывает стандарт C++11. Хорошее дополнение к предыдущей.

Написано с долей юмора, как и всегда:

«Ну, конечно же, вы отлично всё это знаете. Да, да, древняя история: Месопотамия, династия Цинь, Владимир Красное Солнышко (отсебятина переводчика, в оригинале его не было. — Прим. DZ), FORTRAN, C++98... Но времена изменились, а с ними изменились и правила генерации специальных функций-членов в C++».

Крис Х. Паппас, Уильям Х. Мюррей III. Отладка в C++: руководство для разработчиков

512 страниц. Бином, 2001

Chris H. Pappas, William H. Murray, III. Debugging C++. Troubleshooting for Programmers. McGraw-Hill, 2000

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

Книга в основном посвящена разработке под Windows, все примеры, ключи и прочее — относятся к Visual C++. Часто разговор идёт о библиотеке MFC.

Много старья: венгерская нотация, структурные диаграммы, древние справочные системы, <iostream.h> (разнице с <iostream> посвящён целый параграф), DHTML и прочее.

Базовые принципы никогда не устареют (оформление кода, логические vs. синтаксические ошибки), немного ассемблера, много скриншотов. Когда-то и для кого-то книга была хороша, но не сейчас.

Герб Саттер. Решение сложных задач на C++. 87 головоломных примеров с решениями

400 страниц. Вильямс, 2003

Herb Sutter. Exceptional C++. 87 New Engineering Puzzles, Programming Problems, and Solutions. Addison-Wesley, 2002

Содержание:

  • Обобщённое программирование и стандартная библиотека C++
  • Вопросы и технологии безопасности исключений
  • Разработка классов, наследование и полиморфизм
  • Брандмауэр и идиома скрытой реализации
  • Пространства и поиск имён
  • Управление памятью и ресурсами
  • Оптимизация и производительность
  • Свободные функции и макросы
  • Ловушки, ошибки и антиидиомы
  • Понемногу обо всём

Так ли нужен тип bool? Как на практике работает поиск Кёнига? Какие ошибки легко совершить при работе с макросами? Как создать класс регистронезависимой строки? Как эффективно использовать STL? Как сымитировать сложенную функцию (книга написана до C++11, где появились лямбда-функции)? Книга — сборник таких вот вопросов и задач с подробными решениями.

Герб Саттер. Новые сложные задачи на C++. 40 головоломных примеров с решениями

272 страницы. Вильямс, 2005

Herb Sutter. Exceptional C++ Style. 40 New Engineering Puzzles, Programming Problems, and Solutions. Addison-Wesley, 2004

Оглавление:

  • Обобщённое программирование и стандартная библиотека C++
  • Вопросы и приёмы безопасности исключений
  • Разработка классов, наследование и полиморфизм
  • Оптимизация и эффективность
  • Ловушки, ошибки, головоломки
  • Изучение конкретных примеров

В предисловии обсуждается фотография с обложки — вид на Будапешт. Сравниваются архитектура строительства и архитектура программного обеспечения. Своеобразное эссе о строительстве и поддержке (здание парламента на фотографии реставрируют почти с момента постройки в 1902, в особенности скульптуры из плохо подобранного материала).

Затем обсуждается метод Сократа («Цель книги — помочь читателю сделать верные выводы, как из хорошо известного ему материала, так и из только что изученного»). Вполне точно сказано, это правда для книг этой серии.

Почему у шаблонов функций не бывает специализаций? Что такое export и «почему все так старательно игнорируют эту возможность»? Что такое невиртуальный интерфейс (Non-Virtual InterfaceNVI) и зачем он нужен? Почему delete[] не требует передавать размер? Где тогда он хранится? Почему этот код вообще работает?

int main() {
    if( true );        // 1: OK
    if(  42 );         // 2: OK
}

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

Герб Саттер, Андрей Александреску. Стандарты программирования на C++. 101 правило и рекомендация

224 страницы. Вильямс, 2005

Herb Sutter, Andrei Alexandrescu. C++ Coding Standards. 101 Rules, Guidelines, and Best Practices. Addison-Wesley, 2005

Авторы в советах (почти всегда) следуют шаблону: краткое содержание, мотивировка, примеры, исключения, ссылки на литературу.

Книга написана одновременно строгим и человеческим языком, порой с юмором:

«Первое правило оптимизации: не оптимизируйте. Второе правило оптимизации (только для экспертов): не оптимизируйте ни в коем случае».

Содержание: организация, стиль проектирования, стиль кодирования, функции и операторы, классы и наследование; конструкторы, деструкторы и копирование; пространства имён, шаблоны, обработка ошибок и исключений, контейнеры и алгоритмы STL, безопасность типов. При всём при том авторы не стараются написать обо всём на свете, а осознанно выбрасывают вещи, которые, по их мнению, и так должны быть обычной практикой:

«„Не возвращайте указатель или ссылку на локальную переменную“ — хороший совет, но он не включён в данную книгу, поскольку практически все компиляторы выдают соответствующее предупреждение...»

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

Роберт С. Сикорд. Безопасное программирование на C и C++

496 страниц. Вильямс, 2015

Robert C. Seacord. Secure Coding in C and C++. Addison-Wesley, 2013

Оглавление:

  • Бег с ножницами (введение)
  • Строки
  • Уловки с указателями
  • Управление динамической памятью
  • Целочисленная безопасность
  • Форматированный вывод
  • Параллельное выполнение
  • Файловый ввод-вывод
  • Рекомендованные практики

Некоторые главы написаны в соавторстве.

Книга серьёзная. Она посвящена вопросам безопасности для языков программирования C и C++, а также связанных с ними библиотек. В книге не освещаются вопросы безопасности взаимодействия с внешними системами (базами данных, веб-серверами).

Люди писали серьёзные. CERT (Computer Emergency Response Team) — группа в институте Университете Карнеги-Меллон, финансируемом правительством США. Работает с 1988 года, с тех самых пор, когда десять процентов всех интернет-систем поразил червь Морриса. Автор — участник этой группы.

Книга содержит глубокий анализ причин того, почему языки C и C++ иногда .

«В исходном документе по стандартизации языка C содержится ряд руководящих принципов. Из них п. 6 наиболее чётко формулирует источник проблем, связанных с безопасностью. [...] На весеннем совещании WG14 2007 года в Лондоне, где обсуждался стандарт C11, была высказана мысль, что п. а следует перефразировать как „доверяй, но проверяй“. П. б остался нетронутым, так как он критичен для успеха языка программирования C».

Содержание пункта 6:

а) Доверять программисту.

б) Не мешать программисту делать то, что он должен сделать.

в) Язык должен быть небольшим и простым.

г) Предоставлять только один способ выполнения операции.

д) Язык должен быть простым, даже если это не гарантирует переносимость.

Описываются виды поведения,

«Большинство уязвимостей, описанный в этой книге, являются результатом использования в коде неопределённого поведения».

Первая содержательная глава — про строки. И, наверное, неспроста. Строки есть разных видов (обычные, широкие, многобайтовые), понятно что нужно следить за переполнением (эта тема разобрана полностью, вплоть до того, что происходит со стеком и как борются с атаками на уровне компиляторов и операционных систем), но также есть много других проблемных мест:

«Тип строкового литерала с C представляет собой массив символов элементов типа char в C, но в C++ это массив элементов типа const char. [...] иногда компиляторы хранят несколько одинаковых строковых литералов по одному и тому же адресу, так что изменение одного такого литерала может привести к изменению других литералов».

Книга не просто интересная, её можно назвать захватывающей:

«Программа tar использовалась для создания архивных файлов в системах UNIX. В рассматриваемом случае программа tar в системе Solaris 2.0 необъяснимо включала в архив фрагменты файла /etc/passwd, что является примером утечки информации, которая позволяет взломать систему безопасности.

Проблема в данном случае заключалась в том, что утилита tar не инициализировала динамически выделенную память, которая использовалась для чтения блоков данных с диска. К сожалению, перед выделением этого блока утилита tar выполняла системный вызов для просмотра информации о пользователе в файле /etc/passwd. Соответствующий блок памяти освобождался вызовом функции free() так же, как и функция malloc(), не инициализирует (в данном случае — не очищает) используемую память. Эта уязвимость была исправлена путём замены вызова функции malloc() вызовом calloc() в программе tar. Существующее решение чрезвычайно „хрупкое“, так как любые изменения могут привести к тому, что произойдёт утечка важной информации при перераспределении памяти в другом месте программы, вызывая у программиста déjà vul».

Déjà vu — «уже увиденное», déjà vul — от слова vulnerability, «уязвимость».

Всё с понятными объяснениями, всеми нужными картинками и даже комиксами XKCD.

Мэтью Уилсон. C++: практический подход к решению проблем программирования

736 страниц. КУДИЦ-ОБРАЗ, 2006

Matthew Wilson. Imperfect C++. Practical Solutions for Real-Life Programming. Addison Wesley, 2005

Веб-страница книги

Другая книга автора — «Расширение библиотеки STL для C++. Наборы и итераторы»

Краткое содержание: базовые концепции, выживание в условиях реального мира (двоичный интерфейс приложения, таблицы виртуальных функций и т. д.), языковые проблемы, осознанные преобразования, операторы, расширение C++.

Приложения: компиляторы и библиотеки, «Остерегайтесь самомнения!» (рефлексия автора по поводу неправильного использования перегрузки операторов, принципа DRY и других вещей в его прошлых проетках), Arturius (проект, названный в честь короля Артура, известного своим Круглым столом; идея проекта была в том, чтобы код компилировался сразу многими компиляторами, чтобы можно было добиться как можно большей переносимости; сейчас сайт проекта недоступен, на компакт диске — только скриншот и сохранённая веб-страница проекта).

Ещё в самом начале автор говорит, что надеется на то, что читатели знакомы с книгами Мейерса, Саттера и Страуструпа, ссылается на книги этих и других авторов (Дьюхерста, Алекскандреску и др.).

Очень подробно разбирается, как разные компиляторы ведут себя в каких-то ситуациях. Книга многословная, и нельзя сказать, что полезная для всех, скорее — для разработчиков библиотек. Хотя автор уверяет, что всего лишь хочет помочь сделать код более переносимым и менее подверженным ошибкам.

Вот первый содержательный пример из книги.

template< typename D
        , typename B
        >
struct must_have_base
{
       ~must_have_base()
       {
            void(*p)(D*, B*) = constraints;
       }
 
private:
       static void constraints(D* pd, B* pb)
       {
            pb = pd;
       }
};

Здесь на этапе компиляции (закрытая функция статическая) проверяется ограничение, что у одного класса есть другой в качестве базового. И такие вещи всю книгу.

В переводе Batman стал суперменом, Йоги Берра — йогом по имени Берра. Опечаток больше, чем ошибок компиляции, когда случайно пропустишь точку с запятой.

Стив Уэллин. Как не надо программировать на C++

240 страниц. Питер, 2004

Steve Oualline. How Not to Program in C++. 111 Broken Programs and 3 Working Ones, or Why Does 2 + 2 = 5986? No Starch Press, 2003

Оглавление первой части:

  • В начале был...
  • Лиха беда начало
  • Большие хлопоты с маленькими символами
  • Повседневные проблемы
  • Ошибки в стиле C
  • Препроцессор
  • Классы
  • Для настоящих знатоков
  • Ужасы переносимости
  • Несколько рабочих программ
  • Многопоточное программирование и встроенные системы

Вторая часть — подсказки. Третья часть — ответы.

У книги, пожалуй, самое странное посвящение:

«Посвящаю эту книгу моей Чи. Если бы она не вдохновляла меня, я бы никогда не закончил свою работу.

Книга ни в коем случае не посвящается моей жене Карен, потому что мою жену зовут вовсе не Карен. У меня никогда не было жены по имени Карен, и я понятия не имею, о ком идёт речь».

Из введения мы узнаём детали этой истории, но не все.

«Посвящаю эту книгу моей жене Чи Муй Вонг. Если бы она не пошла на курсы программирования и не узнала, что программиста из неё не получится, то этой книги не существовало бы (кстати, автором первой нерабочей программы „Hello, world!“, приводимой в этой книге, был её преподаватель)».

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

К каждой задаче есть подсказка и ответ (в конце книги).

/*******************************************
* "Стандартная" программа Hello world      *
*******************************************/

#include <iostream>
 
void main(void)
{
    std::cout << "Hello world!\n";
}

Это первая задача в книге, но подсказка к ней идёт под номером 228, а ответ — под номером шесть.

Странная особенность книги — постоянные врезки, никак не связанные с задачами и языком C++ вообще.

«Если в чековой книжке кончались пустые бланки, клиент получал бланк у кассира. Конечно, на таком бланке номер счёта указан не был, поэтому клиенту приходилось вписывать его вручную.

Некий мошенник напечатал собственный вариант депозитных бланков. Внешне они ничем не отличались от обычных «общих» бланков, но на них магнитными чернилами был нанесён номер счёта мошенника.

Затем он пошёл в банк и подложил эти бланки в общий лоток.

Афера работала так: клиент приходил в банк, чтобы положить деньги на счёт, и получал один из поддельных бланков. Он заполнял бланк и вносил деньги. Поскольку на бланке был напечатан номер счёта, компьютер автоматически обрабатывал его и вносил деньги на этот счёт. На номер счёта, вручную написанный на бланке, он не обращал внимания. Другими словами, мошенник присваивал чужие депозиты.

Сыщик, которому поручили это дело, был озадачен. Вносимые деньги исчезали, и никто не понимал, как это происходит. Удалось выяснить, что проблема возникает только при внесении денег непосредственно в банке. Сыщик решил попробовать сделать большое количество вкладов и посмотреть, что будет. Поскольку он использовал собственные деньги, ему приходилось ограничиться мелкими вкладами... очень, очень мелкими. Каждым вклад был на сумму в шесть центов. Сыщик потратил целую неделю. Он приходил в банк, заполнял бланк, вставал в очередь, вносил шесть центов, потом заполнял новый бланк, вставал в очередь, вносил шесть центов и т. д. Кассиры решили, что он сошёл с ума. Но вот один из вкладов исчез. Тогда по требованию сыщика в банке проверили, не вносил ли кто-нибудь ещё в этот день сумму шесть центов. Такой вклад нашли, и вора поймали».

«Команда Unix true не делает ничего. Вообще говоря, первая версия программы представляла собой пакетный файл (сценарий) длиной ноль строк. За прошедшие годы в этот файл добавлялась различная информация систем управления исходными текстами, пока программа из нуля строк не приняла следующий вид...»

#! /bin/sh
#
# @(#)true.sh 1.5 88/02/07 SMI; from UCB
#
exit 0

И такие байки, факты, анекдоты и истории из жизни занимают почти полкниги.

17 ноября 2024
Зарегистрируйтесь и войдите, чтобы оставлять комментарии и голосовать.

Книги по C++
Книги Бьерна Страуструпа
Учебная литература. Часть I / Часть II / Часть III. Книги Герберта Шилдта / Часть IV
Русскоязычная литература. Часть I (А—Г) / Часть II (Д—Кр) / Часть III (Ку—О) / Часть IV (П—Я)
Стандартная библиотека шаблонов
Boost
Советы и задачи по языку
Структуры данных, алгоритмы, решение прикладных задач
Что выбрать? (Обновляется)
Также может быть интересным
© MMXI—MMXXV. RSS
 Boosty
Светлая тема / тёмная тема