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.

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



Проверено: beastie ()
Последнее исправление: Aceler (всего исправлений: 3)
Ответ на: комментарий от Andrey_Karpov_2009

Уже лучше. Хотя это уже сто раз обсасывалось в англоязычных кукбуках. ;)

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

притащить новую библиотеку в виде зависимости на GNU/Linux системе практически ничего не стоит - указать новый пакет в зависимостях и поправить конфиги сборочной системы, всё

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

К сожалению, еще есть смена API в разных версиях ... и иногда это такая морока :(

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

Интересно было бы проверить проект MathGL. Сделайте пожалуйста, если будет время.

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

А зачем тащить htop из macports?

А почему нет? Тут скорее уместен вопрос, зачем вообще тащить htop :) Вот на него ответить могу, чисто посмотреть, а то народ пишет все htop-htop, а я его в глаза не видел, вот и приперло хоть посмотреть на это чудо, а под рукой как раз мак был :)

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

Оно собирается. Так что надо гораздо больше.

anc ★★★★★
()

Книга полезная. Еще б epub и вообще была б бомба

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

а под рукой как раз мак был

Понял.

Оно собирается. Так что надо гораздо больше.

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

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

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

Вы куда-то не туда смотрели. Для компиляции как минимум нужен сам компилятор а к мему еще куча всего.

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

Ясно, ты компилишь на лапте с нуля, прости, не разобрал.

Прост:

apt-get build-dep htop 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Gonzo ★★★★★
()
Ответ на: комментарий от Gonzo

Ясно, ты компилишь на лапте с нуля, прости, не разобрал.

Это не я это порты :) Я же написал macports

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

Вы лучше ответьте, занахрена вы обновились с .NET 4.5 до .NET4.6? Чтобы просто отсечь WINE/Windows XP? Если да - то хочется сказать нечто непотребное. Мало того, что обычным одиночным разрабам не продаете, так еще и не даете триалку нигде поставить.

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

Вы лучше ответьте, занахрена вы обновились с .NET 4.5 до .NET4.6? Чтобы просто отсечь WINE/Windows XP? Если да - то хочется сказать нечто непотребное. Мало того, что обычным одиночным разрабам не продаете, так еще и не даете триалку нигде поставить.

PVS-Studio теперь анализирует не только C, C++ проекты, но и C#. Анализ основан на базе Roslyn. А для него нужен .NET 4.6.

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

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

Угу. Только кодеры все такие. Природа человеческая. Бывает, у некоторых самомнение зашкаливает, но на кривизну рук это не влияет. Ты-то наверное, считаешь себя пряморуким?

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

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

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

Гм, «деление» (или «классификация») и «навязывание» — разные слова с разным смыслом.

Адью.

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

Тут в коде явным образом определены контракты — внутри m_pFieldmark должен хранится экземпляр Fieldmark (или производного от него типа).

O'rly?

По get'у и «венгерскому префиксу» «m_p» я б предположил, что с вероятностью в 99% в этом поле хранится указатель на объект, а совсем не объект. И приведение указателя к ссылке - это зашквар.

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

По get'у и «венгерскому префиксу» «m_p» я б предположил, что с вероятностью в 99% в этом поле хранится указатель на объект, а совсем не объект. И приведение указателя к ссылке - это зашквар.

Проссыте, пожалел букф и не написал «внутри m_pFieldmark должен хранится указатель на экземпляр Fieldmark».

По поводу зашквара. Допустим, у нас есть:

class A {...};
class B : public A {...};
и есть что-то вроде:
void f(A * obj) {
  B * b = dynamic_cast<B *>(obj);
}
Тогда b может получить значение nullptr в двух ситуациях:

1. obj сам nullptr.

2. obj указывает не на B и не на наследника B (это может быть указатель на A или указатель на другого наследника A).

В некоторых случаях мы точно знаем, что в f всегда передается ненулевой указатель на B, а не на что-то другое. Почему именно так — это уже другой вопрос. Например, f имеет именно такой прототип дабы удовлетворить какому-то уже существующему интерфейсу, придуманному задолго до нашего знакомства с C++. Или из-за каких-то других факторов. Это не важно.

Важно то, что в f мы не хотим сталкиваться с ситуациями, когда dynamic_cast приводит к nullptr. Мы хотим получить некоторую защиту: либо ссылку на B, либо исключение. Записывается это непосредственно в синтаксисе C++:

void f(A * obj) {
  B & b = dynamic_cast<B &>(*obj);
}
В ряде случаев это вполне обоснованно и разумно. И это вполне себе запись контракта: obj должен указывать на объект типа B или наследника B. Если контракт нарушен, то нужно бросать исключение. Если это исключение из деструктора, то «глотать» его нет смысла — проблема не в том, что в деструкторе обнаружили нарушение контракта. А в самом факте этого нарушения, которое возникло гораздо раньше.

eao197 ★★★★★
()

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

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

Проссыте, пожалел букф и не написал

Телепаты в отпуске, так что просирайся сам.

В некоторых случаях мы точно знаем, что в f всегда передается ненулевой указатель на B

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

Важно то, что в f мы не хотим сталкиваться с ситуациями, когда dynamic_cast приводит к nullptr.

Ты же предложением выше мамой поклялся, что никаких nullptr не будет?

Мы хотим получить некоторую защиту: либо ссылку на B, либо исключение.

Ну и что и от чего ты «защитил», прости господи? Свой манямирок от суровой реальности своего же кода? Что за убогое жава-мышление, что выброс исключения посреди нигде - это «защита»? Или ты гарантируешь, что весь твой код 100% exception safe с точки зрения прикладной логики?

Если у тебя там ВНЕЗАПНО окажется nullptr, чего «никогда быть не может», ну так разыменуй его, получи свой null pointer exception с нормальным стек-трейсом, коркой и иди править баг, а не выёживайся. Пользователю абсолютно фиолетово - улетела ли твоя программа по сегфолту или по необработанному исключению - она один хер не работает.

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

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

В некоторых случаях мы точно знаем, что в f всегда передается ненулевой указатель на B

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

Мне стыдно говорить LOR-овскому иксперду со столь большим LOR-овским стажем и таким количеством звезд, что замена dynamic_cast на static_cast чревата, например, вот в таких ситуациях:

class A {...};
class M {...};
class B : private M, public A {...};
...
A * ptr = get_pointer_from_somewhere();
B * b = dynamic_cast<B*>(ptr);

Полагаю, что раз вы об этом не подумали, то вряд ли поняли, о чем именно я вам говорил.

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

Такой код:

class B : private M, public A {...};

уже «чреват» сам по себе, без всякой замены кастов с одного на другой. Такой код - это уже диагноз проекту.

вряд ли поняли, о чем именно я вам говорил.

Ну, давай еще раз:

1. Мы получаем аргументом в функцию указатель на базовый класс, который 100% указывает на производный тип Т.
2. И если он не указывает на тип Т, мы создадим ссылку на объект, чтобы выбросило исключение.

Что не верно?

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

Нужно заменить «100%» на «мы уверены».

Это какая-то особая логика? Возможно женская? Потому как в обычной логике программирования «мы уверены» = «мы проверили все точки вызова, и делаем дедуктивное утверждение на основе полной индукции».

Любые другие варианты маркируются как «ХЗ».

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

Это какая-то особая логика? Возможно женская?

А вы почему интересуетесь? Испражняетесь в остроумии, пытаетесь доказать, что у вас длиннее (никто в этом даже не сомневается) или же вам интересна техническая сторона дела?

Если брать техническую сторону, то какой-то вы стремный собеседник: то предлагаете dynamic_cast на static_cast заменить, то механизм подмешивания у вас является маркером пушного зверька на проекте...

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

Ключевое слово «все». Кто дает гарантии того, что проверили все точки вызова? Как эти гарантии были получены? Покрыли тестами? Применили статический анализатор? Использовали какие-то методы верификации?

Когда у нас есть:

void f(B &b) {...}
мы знаем, что в f работаем с B (или его наследником).

Когда у нас есть:

void f(A *a) {...}
но внутри f() нам нужно работать с B, то мы можем думать (можем быть уверены), что реально a указывает на объект B и делать dynamic_cast. Но что делать если мы ошибаемся? И даже если мы не ошибаемся в версии 1.0, то не изменится ли ситуация в версии 2.0, когда к проекту подключатся новые разработчики? Как в коде записать контракт о том, что внутри f после каста у нас должен оказаться B?

Если я правильно вас понял, вы предлагаете делать так:

void f(A *a) {
  B * b = dynamic_cast<B*>(a);
  b->do_something(); // Да и похрену, если получили null.
                     // Свалимся в корку и потом будем колупаться с coredump-ом.
}

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

Я предпочитаю делать так:

void f(A *a) {
  ensure( a );
  B & b = dynamic_cast<B &>(*a);
  b.do_something();
}

Разница принципиальная. Во-первых, если do_something — это невиртуальный метод, то вызов do_something для нулевого указателя на объект не обязательно приведет к падению в корку именно в месте вызова. Скорее всего падение будет где-то внутрях do_something при попытке обращения к нестатическим данным этого объекта. Что может произойти не сразу и исходную причину придется искать гораздо дольше, чем вам представляется.

Во-вторых, представьте себе многопоточное приложение. Ну, например, текстовый процессор. В котором один из потоков занялся сохранением документа в фоновом режиме. А в еще одном из потоков вызывается метод f() и там мы нарываемся на сегфолт. В вашем случае падает все приложение, теряя все несохраненные данные. В случае же с dynamic_cast-ом и исключением bad_cast есть возможность перехватить исключение и завершить сохранение документа.

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

Ключевое слово «все». Кто дает гарантии того, что проверили все точки вызова?

Ты давал:

В некоторых случаях мы точно знаем, что в f всегда передается ненулевой указатель на B, а не на что-то другое.

Ну и кто из нас теперь «стрёмный собеседник»? С такими-то манёврами?

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

Мне представляется, что ты ни разу не занимался отладкой сколько-нибудь крупных программ, иначе бы не нёс такой чепухи. Начать можно прямо с того, что для перехваченных исключений нет stack trace, его получение надо велосипедить самому для каждой платформы.

Искаропки считать корку gdb и получить точную точку падения в разы легче, чем гадать на кофейной гуще, откуда именно прилетел std:bad_cast.

Во-вторых, представьте себе многопоточное приложение. Ну, например, текстовый процессор.
В случае же с dynamic_cast-ом и исключением bad_cast есть возможность перехватить исключение и завершить сохранение документа.

Ой, всё! Я не готов обсуждать школофантазии, тем более, что я уже говорил по этому поводу:

Или ты гарантируешь, что весь твой код 100% exception safe с точки зрения прикладной логики?

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

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

С такими-то манёврами?

Ну давайте пойдем по второму кругу. Итак: «В некоторых случаях мы точно знаем, что в f всегда передается ненулевой указатель на B, а не на что-то другое. »

Как вы предлагаете выразить это знание в коде? Мой вариант такой:

void f(A *a) {
  B & b = dynamic_cast<B &>(*a);
  b.do_something();
}

Ваш вариант?

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

Я уже признал, что у вас длиннее.

Или ты гарантируешь, что весь твой код 100% exception safe с точки зрения прикладной логики?

Дяденька, вы разработкой софта занимаетесь или измерением длины? Я, например, ни разу не видел кода, который бы что-то на 100% гарантировал. И более менее представляю, сколько стоит разработка более-менее надежного кода. Посему ваш вопрос, в лучшем случае, является риторическим.

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)

Буря отшумела, спокойно спрошу теперь.

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

Zmicier ★★★★★
()
3 июля 2016 г.
Ответ на: комментарий от Zmicier

что вас заставило написать небольшую несвободную книгу?

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

Если честно, вопрос меня поставил в тупик. Я не знаю. У меня небыло какого-то плана, когда я её писал. Как-то само собой получилось. Было несколько предпосылок к написанию: отчасти был готов разрозненный материал; есть задача рекламировать PVS-Studio; мне хотелось поделиться с миром моим видением по некоторым вопросам; эта книга зачтется мне, когда я буду номинироваться в очередной раз на Microsoft MVP. Все вместе и дало толчок к написанию.

По поводу несвободности... Я про это тоже не думал. Мне просто всё равно. Но, пожалуй, пусть она будет именно несвободной. Возможно со временем я расширю этот материал и попробую опубликовать книгу в печатном виде. Просто так, ради интереса. Например, буду раздавать на конференциях для привлечения внимания.

Andrey_Karpov_2009
() автор топика

По поводу несвободности... Я про это тоже не думал. Мне просто всё равно.

Это печально. В некотором роде это самое печальное, что мы имеем — не корыстный интерес (он понятен), а тупое безразличие к свободе своих пользователей / читателей.

Тем не менее, спасибо, что ответили.

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