LINUX.ORG.RU

Написал небольшую книгу для C/C++ программистов

 , , , ,


13

8

Здравствуйте. Меня зовут Андрей Карпов. Сфера моих интересов - язык C/C++ и продвижение методологии статического анализа кода. На протяжении пяти лет я являюсь Microsoft MVP в номинации Visual C++. Основная цель моих статей и работы, сделать код программ немножко безопасней и качественней. Буду рад, если эта мини-книга научит вас писать более надежный код и предостережет от некоторых типовых ошибок. Немало полезного здесь можно будет почерпнуть и тем, кто занимается написанием стандартов кодирования для своих компаний.

Немного истории. Не так давно я создал ресурс, на котором делился различными полезными советами по программированию на языке С++. Ресурс не собрал ожидаемое количество подписчиков, поэтому я не вижу смысла приводить здесь на него ссылку. Сайт просуществует какое-то время, после чего уйдет в небытие. А вот советы достойны сохранения. Поэтому я доработал, пополнил эти советы и объединил их в единый текст. Желаю приятного чтения.

UPD: PDF-версия: https://yadi.sk/i/RCHauHFBr2cSs

P.S. Пользуясь случаем приглашаю всех желающих последовать за мной в Twitter: @Code_Analysis.

>>> Главный вопрос программирования, рефакторинга и всего такого

Ответ на: комментарий от denton

хехе, намедни имел счастье провести экспермент в этом духе.

С целью ускорить работу системы стали накидывать ей процессоры и озу. Начали с 8 гб и 4 ядер. Дошли до 24 ядер и 64 гб озу.

Примерно на 30 гб система перестала набирать используемую память. Совсем. Примерно с 16 ядер рост быстродействия полностью остановился.

Пришлось вернуться к оптимизациям запросов...

AVL2 ★★★★★ ()
Ответ на: комментарий от cipher

а что происходит после первой звезды? Ты сам начинаешь оскорблять?

arcanis ★★★ ()
Ответ на: комментарий от znenyegvkby

Узрел в твоей предыдущей грамоте одни сатанинские каракули вместо православных Богом данных нам слов. Уважай труд Кирилла нашего и Мефодия, первопечатника Федорова, собирателя Даля и Ожегова ныне, присно и во веки веков!

AVL2 ★★★★★ ()
Ответ на: комментарий от AVL2

Oh, shit

Ах, вы об этом. Я уж было подумал, что вы не поняли сарказма, и решили что я их оскорбляю.

Уважай труд Кирилла нашего и Мефодия

Виноват, исправлюсь!

znenyegvkby ()

29. Используйте для итераторов префиксный оператор инкремента (++i) вместо постфиксного (i++)

34. Undefined behavior ближе, чем вы думаете.
Рассмотрим синтетический пример кода:

for (size_t i = 0; i != Count; i++)
  array[index++] = char(i) | 1;

Поправьте, пожалуйста.

userd ()
Ответ на: комментарий от gag

Я, как и Кнут, считаю, что преждевременная оптимизация - корень всех зол.

И как бы от неё вовсе избавиться в коде?

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

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

У меня такая мечта: писатели компиляторов составляют справочник по тому, как на С и на С++ записывать типовые микро-задачи. Т.е. как с макроассемблером.

Компиляторы давно уже умеют обнаруживать типовые последовательности действий.

такой рефакторинг кодовой базы компиляторов, чтобы программисты стали в состоянии добавлять в них шаблоны для новых обнаруженных микро-задач. В итоге - код остаётся насколько возможно обобщённым, а оптимизирующий компилятор занимается своей задачей - оптимизацией. Утопия?

Оно не то, чтобы утопия, оно просто не нужно. Люди, обладающие квалификацией уровня «умеет добавить оптимизирующее правило компиляции» наверняка знают, какой код компилятор транслирует эффективно. Что может быть полезным - это развитая макросистема или CTFE.

tailgunner ★★★★★ ()
Ответ на: комментарий от Aceler

«Ничего не найдено
Возможно, владелец удалил файлы или закрыл к ним доступ.
А может быть, вам досталась ссылка с опечаткой.»

mango ★★ ()
Ответ на: комментарий от arcanis

Появляется возможность редактировать сообщения.

cipher ★★★★★ ()
Ответ на: комментарий от userd

Поправьте, пожалуйста.

Что именно? Честно не понял, на какой ляп Вы указываете.

Andrey_Karpov_2009 ()
Ответ на: комментарий от znenyegvkby

Неужели на Си/Си++ так принято писать? Я всю жизнь учил людей и сам учился переходить к циклу или функции даже для 2 повторяющихся фрагментов кода.

Т.е. о производительности в _реальных_ проектах вы не слышали, а только _учили_ писать код студентов. Окей, так и запишем.

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

Attila ()

У меня на этот материал большие планы в плане рекламы PVS-Studio

Все ясно, расходимся.
Прибейте уже топик, как спам.

iSage ★★★ ()
Ответ на: комментарий от Andrey_Karpov_2009

Спорно. Если не использовать вообще библиотек - можно про***ть все сроки разработки проекта. Но и тащить вал библиотек причем сомнительного качества не следует. Тут где-то должна быть золотая середина, которая определяется вменяемостью разработчиков.

Впрочем, если приложение на С/С++ разрабатывается под windows, то там обычно в моде тащить «проверенные» версии с запашком, самому разруливать зависимости между библиотеками, так как если я правильно помню тамошние реалии - у них до сих пор нет аналога pkg-config, а появившаяся недавно пакетная система не пакетирует библиотеки с хедерами. Потому в windows реалиях это правило как раз очень даже полезно.

ncrmnt ★★★★ ()
Последнее исправление: ncrmnt (всего исправлений: 1)
Ответ на: комментарий от debugger

Поправочка: язык программирования их *создаёт*.

Какое интересное мировоззрение... А можно пример: какой язык, какие проблемы и каким образом?

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

Ну так используй нормальный язык в своем нормальном проекте, и не навязывай остальным свои предпочтения по ЯП. Да и что вообще С++-хейтер забыл в такой теме?

segfault ★★★★★ ()
Ответ на: комментарий от slonopotamus

1. Да запросто: она никак не клеится с давно наблюдаемыми в квантмехе фактами. А значит, либо не учитывает еще кучу факторов, либо в корне не верна.

2. Я ж сказал: критикоать много ума не надо.

3. Я бы тоже покритиковал. Но для начала давайте определимся, о чем вообще речь - о чистом rust без unsafe-ов, или написанием низкоуровневого кода в лучших традициях С, но только с использованием синтаксиса rust?

segfault ★★★★★ ()
Ответ на: комментарий от Andrey_Karpov_2009

+ RazrFalcon
Использован постфиксный инкремент итератора(i++) вместо префиксного, хотя предыдущий совет состоял в том чтобы такого избегать.

userd ()
Ответ на: комментарий от userd

Так написано же, что только для итераторов. В коде index и i - это просто числа. Их это не касается и для них разницы нет.

RazrFalcon ★★★ ()
Ответ на: комментарий от RazrFalcon

Понятно, что для обычных чисел замена оператора инкремента на префиксный не даст прироста производительности.
Но если ВСЕГДА использовать именно префиксный инкремент, во-первых, сложнее забыть о том что его таки нужно применять для перегруженных итераторов, во-вторых, сохраняется общий стиль кода, и у читателя не возникает вопрос «Почему здесь использован именно префиксный инкремент?»

userd ()
Ответ на: комментарий от cipher

Ты приходишь, мы тебя оскорбляем.

Тут да, хочется матом ругаться.

http://www.viva64.com/ru/b/0391/

И?

DllMain

Инициализация COM-потоков с помощью CoInitializeEx. При определенных условиях данная функция может вызвать LoadLibraryEx.

Вызов функций из библиотек User32.dll или Gdi32.dll. Некоторые функции загружают другие DLL-библиотеки, которые могут быть не инициализированы.

Создание именованного конвейера или другого именованного объекта (только для Windows 2000). В Windows 2000 именованные объекты предоставляются библиотекой Terminal Services DLL. Если данная библиотека не инициализирована, ее вызовы могут привести к аварийному завершению процесса.

Указатель хотят сдвинуть на определённое количество байт. Этот код будет корректно работать в Win32 программе, так как в ней размер указателя совпадает с размером типа long.

Как правило, для программ выделяется не так уж и много стековой памяти. По умолчанию, когда вы создаёте проект в Visual C++, в настройках указано использовать стек размером всего 1 Мегабайт, поэтому функция alloca() очень быстро может исчерпать всю доступную стековую память, если она располагается в теле цикла.

Ну и т.д.

И как это относится к Linux и OpenSource? Или FOSS можно собирать под Windows и запускать в Wine? И почему в статье так много уделяется PVS-Studio? И почему у Andrey_Karpov_2009 на автарке какой-то лысый и очень некрасивый мужик в майке с все той же надписью PVS-Studio? Если это какая-то реклама, то лучше одеть майку с надписью PVS-Studio на красивую девушку, а не на лысого мужика, который похож на гопника, разве нет?

Root-msk ★★★★★ ()

Чет я не могу скачать книго

Ничего не найдено
Возможно, владелец удалил файлы или закрыл к ним доступ.
А может быть, вам досталась ссылка с опечаткой.

Xintrea ★★★★★ ()
Ответ на: комментарий от Andrey_Karpov_2009

Если человек тянет библиотеку криптографии/архивирования только из-за того, что ему потребовался CRC32 - то да, шлёпать и заставлять писать свой вариант (скопировать нужный кусочек из достойного места).

Бредятина да и только. В любом коде могут быть ошибки включая подсчет CRC32, сколько шансов что найдут ошибку в «библиотеке» и сколько в коде скопированном из «достойного места» ? И сколько шансов что вы вообще будите читать форум этого «достойного места» после того как скопируете код? А вот либа обновиться с большей вероятностью.

anc ★★★★★ ()
Ответ на: комментарий от AVL2

Узрел в твоей предыдущей грамоте одни сатанинские каракули вместо православных Богом данных нам слов. Уважай труд Кирилла нашего и Мефодия, первопечатника Федорова, собирателя Даля и Ожегова ныне, присно и во веки веков!

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

Pm7vLB ()
Ответ на: комментарий от segfault

3. В тех местах, где это дает существенный буст, вполне оправдан unsafe. Но бОльшая часть кода в этом не нуждается и будет содержать меньшее количество багов. Я последние годы много пишу на C++ и часто вижу допускаемые мной и коллегами баги, которые были бы принципиально невозможны, используй мы язык с лучшими гарантиями memory safety.

slonopotamus ()
Ответ на: комментарий от Gonzo

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

Фигня. Я вот htop недавно из macports решил поставить, так офигел сколько оно за собой притянуло, час наверное качала/компиляла.

anc ★★★★★ ()
Ответ на: комментарий от segfault

А можно пример: какой язык, какие проблемы и каким образом?

Да пожалуйста. Если читаешь новости о новых дырах в софте, то знаешь, что первопричина половины дыр — переполнение буферов в программах на C/C++. Дальше сам догадаешься?

Ещё пример: время компиляции больших программ на C++.

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

Покажи пальцем, где я тебе что-то навязывал, а за остальных вообще помолчи.

Да и что вообще С++-хейтер забыл в такой теме?

NYB

debugger ()
Последнее исправление: debugger (всего исправлений: 1)
Ответ на: комментарий от userd

Афигенный пример. for (size_t i = 0; i != Count; i++)
Больше всего доставило в разделе «Корректный код». Привет бесконечный цикл, давно не виделись.

anc ★★★★★ ()
Ответ на: комментарий от Oxdeadbeef

Это частный пример был, но он имеет место быть.

anc ★★★★★ ()
Ответ на: комментарий от Oxdeadbeef

Homebrew слишком хипстерский. Какие-то руби там. Чур-чур меня) Тоже по-старинке использую MacPorts. В общем, доволен.

dave ★★★★★ ()
Ответ на: комментарий от anc

Афигенный пример. for (size_t i = 0; i != Count; i++) Больше всего доставило в разделе «Корректный код». Привет бесконечный цикл, давно не виделись.

Почему бесконечный?

Andrey_Karpov_2009 ()
Ответ на: комментарий от Andrey_Karpov_2009

Патамучто != не > не < а именно != и если i по какой-то причине в цикле будет изменена то привет....

anc ★★★★★ ()
Ответ на: комментарий от anc

Патамучто != не > не < а именно != и если i по какой-то причине в цикле будет изменена то привет....

Она не меняется в цикле. Нашли к чему придраться. Так бы и написали, что этот код плох тем-то и тем-то. А то сразу «код неправильный». И мы тут гадаем, что такое Вы увидели, что не видим мы.

P.S. != - вполне нормальная и распространённая практика. Вы просто с ней видимо не сталкивались. Такое написание как раз подчеркивает, что индекс будет изменяться только в одном месте. Т.е. мы просто поочерёдно переберем все значения индекса, не перепрыгивая. А когда встречается < - надо сразу насторожиться и вникать в тело цикла. Ибо изменение индекса внутри тела — это плохой стиль и надо быть внимательным.

Andrey_Karpov_2009 ()
Ответ на: комментарий от Andrey_Karpov_2009

!= - вполне нормальная и распространённая практика. Вы просто с ней видимо не сталкивались.

Вы видимо под дос и win < NT не кодили судя по всему, там эта «вполне нормальная и распространённая практика» в случае ошибки приводила только к одному - ребут.
В данном случае вы представляете это как учебник. И в нем даете конкретный пример , далее этот пример «из достойного места» берет юный падаван, добавляет несколько условий и ооочень удивляется...
Классика говорит: водите условия предваряющие ваши же ошибки <= и т.д.
ЗЫ И не забываем что «учите» вы вроде как С, где в результате «утечка памяти компенсируется ее перезакладом в другом месте» :)

anc ★★★★★ ()
Ответ на: комментарий от Andrey_Karpov_2009

И мы тут гадаем, что такое Вы увидели, что не видим мы.
мы тут
не видим мы

Не рано ли корону мерять? маловат еще.

anc ★★★★★ ()
Ответ на: комментарий от Andrey_Karpov_2009

вам (фирме уцелом) всёж стоит по стилю чёнить полистать - тех же Кернигана&Плоджера

qulinxao ★★☆ ()

Ну вот придирка к такому коду:

virtual ~LazyFieldmarkDeleter()
{
  dynamic_cast<Fieldmark&>(*m_pFieldmark.get()).ReleaseDoc(m_pDoc);
}
выглядит более чем странной. Тут в коде явным образом определены контракты — внутри m_pFieldmark должен хранится экземпляр Fieldmark (или производного от него типа). Если это не так, то бяда-бяда. Соответственно поэтому используется dynamic_cast<T&>, а не dynamic_cast<T*>. И исключение в деструкторе — это отличный способ дать знать о проблеме, а не прятать ее под ковер. Тем более в случае раскрутки стека в результате какого-то исключения: если при этом выясняется, что инварианты класса LazyFieldmarkDeleter нарушены и в m_pFieldmark не экземпляр Fieldmark, то terminate() гораздо лучше, чем обрамление dynamic_cast-а try-catch-ами или использование dynamic_cast-а для приведения указателей.

Так что совет на счет внимательного отношения к исключениям из деструктора — это хорошо. А вот пример никуда не годится. Так вы людей плохим практикам учите.

eao197 ★★★★★ ()
Ответ на: комментарий от debugger

Да пожалуйста. Если читаешь новости о новых дырах в софте, то знаешь, что первопричина половины дыр — переполнение буферов в программах на C/C++. Дальше сам догадаешься?

1. Далеко было ходить лень, взял крайние 10 новостей об уязвимостях на ЛОРе, и лишь в одной из них причина - переполнение буфера.
2. Переполнения буффера порождает не язык, а криворукие кодеры, так что за пример не канает.

Ещё пример: время компиляции больших программ на C++.

Гентупроблемы.

Покажи пальцем, где я тебе что-то навязывал, а за остальных вообще помолчи.

Вот тут кое-кто делил языки на «нормальные» и «ненормальные».

segfault ★★★★★ ()
Ответ на: комментарий от segfault

2. Переполнения буффера порождает не язык, а криворукие кодеры, так что за пример не канает.

Согласен на все 146! Сам такой. Н-цать лет назад появилось время для рефакторинга кода написанного еще н-цать лет до этого, так выяснил что там тупо утечка в одном месте компенсировала недостачу в другом :) А криворукости в момент написания хватало, я коллегу потом спрашивал «какого он например выделил памяти ровно 50 байт?» ну естественно он уже не помнил к тому моменту. :)

anc ★★★★★ ()
Ответ на: комментарий от slonopotamus

В тех местах, где это дает существенный буст, вполне оправдан unsafe.

Ровно как и на плюсах вполне оправдана низкоуровневая работа с ресурсами только там, где это дает существенный буст.

Я последние годы много пишу на C++ и часто вижу допускаемые мной и коллегами баги, которые были бы принципиально невозможны, используй мы язык с лучшими гарантиями memory safety.

Возможно, вы с коллегами действительно пишете не на «вашем» языке.

Я больше 5 лет работаю С/С++ разработчиком, но серьезных проблем при работе с памятью в плюсах так и не повидал. Да и чем память так принципиально отличается от других ресурсов?

А чье-то желание неуместно попонтиться хитро оптимизированной функцией обычно пресекается на этапе code review. И это же актуально для того же rust, который вовсе не запрещает злоупотреблять unsafe-ами.

Проблемы с указателями и выезды за буфер вылазили в проектах на С, но это был жесткий embedded, где каждый такт и каждый килобайт на счету, так что приходилось крутиться, да.

segfault ★★★★★ ()
Ответ на: комментарий от segfault

но это был жесткий embedded

Вот где больше всего быдлокода я видел так это в нем. Но там проще, заметнее так сказать.

anc ★★★★★ ()
Ответ на: комментарий от segfault

серьезных проблем

Что такое «серьезная проблема»? Проблем с памятью всего несколько: null pointer dereference, memory leak, use-after-free и выход за границы массивов. И из них более-менее безболезненно приложение может пережить только memory leak. А все остальные - кровькишкираспидорасило. Да, я значю как их диагностировать и локализовать в коде, но предпочел бы чтобы они вообще были невозможны.

slonopotamus ()
Ответ на: комментарий от slonopotamus

Проблем с памятью всего несколько: null pointer dereference, memory leak, use-after-free и выход за границы массивов.

Ну это как 2 проблемы в электронике: отсутствие контакта там, где он должен быть и его наличие там, где не должно быть.

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

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

Но это все касательно выбора языка «для себя». Когда выбирают ЯП для коммерческого проекта, то руководствуются более прагматичными доводами, нежели «мне больше нравится $LANGUAGE_NAME_1», или «Я как-то пробовал $LANGUAGE_NAME_2 и прострелил себе ногу».

segfault ★★★★★ ()
Ответ на: комментарий от anc

Вот где больше всего быдлокода я видел так это в нем. Но там проще, заметнее так сказать.

Мда, мне еще повезло работать в команде из людей, которые умеют эти самые ошибки находить и эксплуатировать. Не представляю, что творится в embedded-е, не связанном с информационной безопасностью.

segfault ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.