Comments 61
void main()
{
float A = 1.0 / tan((3.141592538 / 180.0) * 5.0f / 2);
float B = 1.0 / tan((3.1415926538 / 180.0) * 5.0f / 2);
printf("A=%f\nB=%f\n", A, B);
}
Выдаст:
A=22.903767
B=22.903765
Да, последнее десятичное число за пределом погрешности. Но важно само наличие разницы.
Во втором случае тоже ошибка c пропуском цифры константы, pi = 3.14159265358…
Почему не следуете своему же совету и не пишете M_PI?
void main()
{
float A = 1.0 / tan((3.141592538 / 180.0) * 5.0f / 2);
float B = 1.0 / tan((M_PI / 180.0) * 5.0f / 2);
printf("A=%f\nB=%f\n", A, B);
}
Выдаёт:
A=22.903767
B=22.903765
Другое дело, что whitemonkey, видимо, спрашивал про то, что во флоате этой разницы уже нет в принципе, то есть
assert(3.1415926f == 3.1415925f); // ok
assert(3.1415926 == 3.1415925); // а вот тут assert не пройдет
И тогда ошибка найдена очень правильно, так как хоть результат и пишется в float, а вычисления в tan-то проходят в double.
3.141592538 // В коде.
3.1415926538 // Я исправил 1 ошибку.
3.14159265358 // А их там две! Ещё и 5 перед 8 нет.
Я имел в виду, что эта вторая ошибка вообще уже никак не влияет на результат.
www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0631r8.pdf
например в msvc 2019 16.5 завезут #include
Если я правильно понимаю фактическая ошибка вызвана вот этой строкой:
TITLE(Book::tr("Logarithmic Base Conversion"))
То есть анализатор должен был сообщить о ней либо там где описывается реализация
TITLE
либо там где описывается
Book::tr(
я что-то упускаю?
И если она в строке TITLE, почему анализатор нашел ее именно тут, а не при анализе кода в другом месте где описывается функционал этого TITLE?
Я имею в виду что если ошибка в макросе. То анализатор должен бы ругаться там где декларируется макрос. Нет?
Можете мне подсказать, что в этом фрагменте кода надо исправить чтобы он стал валидным?
Я имею в виду что если ошибка в макросе. То анализатор должен бы ругаться там где декларируется макрос. Нет?Ругаться на содержимое макросов часто невозможно. Пока они не раскроются, неизвестно, верный получится код или нет. Но даже, если не верный, всё равно может быть виноват не сам макрос, а использующий его макрос/код. Или макросу может быть передан неправильный аргумент, на который макрос не рассчитан. Не знаю как просто объяснить.
В PVS-Studio есть диагностики на тему макросов (пример V1003). Но их мало. В основном работа всегда идёт с уже препроцессированных кодом.
Можете мне подсказать, что в этом фрагменте кода надо исправить чтобы он стал валидным?Нужно исправить один из макросов.
Макросы — такая штука, что невозможно сказать, валидный он или нет (за исключением синтаксиса самого определения).
Например
#define MY_MACRO while(somevar) doBlaBla()
И что теперь? Если somevar
и doBlaBla
определены в месте вызова, то всё пойдёт как по маслу. А если нет, то возникнет ошибка.
А ещё если макрос не используется вообще, то в нём хоть синтаксически бред будет — компилятору пофиг. Этакий код Шрёдингера — пока не вызовешь, ошибка и есть, и нет.
Поэтому в плюсах от макросов и избавляются всеми силами (благо можно). В си с этим хуже.
P.S.: при всём том, анализатору бы было неплохо показать раскрытый макрос для ясности происходящего.
Спасибо за статью.
Что любопытно, что в современных языках половина ошибок была бы отловлена на этапе компиляции
(9 8 6 5 3)
Нет, C# не современный. Современные — Го, Раст, Свифт, Идрис и прочие. Появившиеся в последние 10 лет.
И даже го конечно пропускает часть из этих проблем, но не все (раз, два, 9 записать невозможно, остальное не проверял).
А про C#: он хоть и старенький (20 лет это приличный срок), у него есть секретное оружие — рослин и его анализаторы. Примерно как PVS, только если вас бесит какая-то проблема вы можете самостоятельно за пару часов напилить анализ который будет проверяться компилятором, и не придется ждать пока ребята из PVS решат что стоит такую проверку запилить. Буквально: сделали репу, написали что анализировать и должно ли это быть ворнингом/ошибкой, опционально запилили фикс, запушили пакет, добавили себе в проект, нашли все проблемные места. С момента как захотелось зафиксить какую-то фигню до момента как проект для сборки потребовал все такие места починить — буквально час-два.
А какой процент разработчиков сидит на clang (причем я так понимаю он должен быть довольно свежим?). Мне казалось большинство используют gcc.
Ну и второй вопрос насколько это удобно. В дотнета весь тулинг на это завязан, потому что language server всё это учитывает когда не только при сборке ошибку выдает, но и подкрашивает ошибочные места, и предлагает исправление. Как с этим дела обстоят у этого analyzer API?
Но вообще я не знал, что такая штука есть. Это круто.
Никто не мешает использовать gcc для сборки продукта, а clang — для анализа в процессе разработки (можно и в дополнение к тому же pvs или coverity). Что касается тулинга — лично у меня это как правило Qt Creator, он хорошо интегрирован с clang, можно запустить анализ прямо из IDE с теми настройками, с которыми хочешь, будет окошечко со списком обнаруженных проблем, из которого можно перейти на соответствующий кусок кода. Quick fix'ы там, по-моему, тоже предлагаются, если они возможны, но я ими ни разу не пользовался. Вот так это примерно выглядит :
https://doc-snapshots.qt.io/qtcreator-4.0/creator-clang-static-analyzer.html
Никто не мешает использовать gcc для сборки продукта, а clang — для анализа в процессе разработки
Мне казалось что как правило гцц проект clang собрать не в силах.
Что касается тулинга — лично у меня это как правило Qt Creator, он хорошо интегрирован с clang, можно запустить анализ прямо из IDE с теми настройками, с которыми хочешь, будет окошечко со списком обнаруженных проблем, из которого можно перейти на соответствующий кусок кода.
Не совсем понял, как сделать свою диагностику. На сайте http://clang-analyzer.llvm.org/ говорят "помогайте нам улучшить диагностики, присылайте PR'ы". Но они явно не примут анализ из разряда "Проверка что код для КлиентаА в проекте My.Company.Name.ClientA имеет зависимости от кода для КлиентаБ My.Company.Name.ClientB".
Мне казалось что как правило гцц проект clang собрать не в силах.
Ну это разве что если там используются gcc-специфичные расширения типа нижеупомянутого constexpr интринсика acos(). Но обычно так все-таки стараются не делать, а все-таки стараются писать универсальный код. Кстати, сам факт проверки сборкой на различных компиляторах этому очень способствует.
Не совсем понял, как сделать свою диагностику.
clang-analyzer.llvm.org/checker_dev_manual.html
Вот пример такого плагина-чекера:
github.com/Lekensteyn/clang-alloc-free-checker
Вот я даже нашел ту недавнюю статью, про которую выше писал:
habr.com/ru/company/dsec/blog/473412
Понял. Любопытно, спасибо. Хотя юзабилити немного страдает.
Ну типичные задачи:
- как запускать анализаторы a, b, c, d автоматически при билде любого проекта в IDE
- как запускать анализаторы x, y, z только при билде проектов Y и Z
- как хранить настрйки анализаторов в репозитории чтобы при клонировании репы пользователь автоматически конфигурировал локальную сборку с соответствующими анализаторами (аналогично, как
.gitattributes
конфигурирует локальные настройки гита для конкретного репозитория).
Не спорю, но сколько пользователям эту диагностику ждать? Месяц? Два? Полгода? Плюс у вас же свои приоритеты, тогда я свою хотелку не получу никогда.
С другой стороны вот я в качестве примера: человек попросил от языковой команды определенную фичу, я ему показал, что её самостоятельно можно сделать за час. За час — просто потому, что кода буквально полсотни строк.
Всё же это совсем разные сроки.
И наконец, как я чуть выше написал, некоторые аналитики имеют смысла в разрезе конкретного проекта. Кстати, пример не надуманный, у меня на одном проекте была именно такая проверка, что компоненты написанные для одного клиента не используются в компонентах другого клиента. Это приводило к ошибке компиляции и нужно было с этим явно что-то делать, например, рефакторить компонент и выносить его в Common проект.
Не спорю, но сколько пользователям эту диагностику ждать? Месяц? Два? Полгода? Плюс у вас же свои приоритеты, тогда я свою хотелку не получу никогда.Становитесь клиентом и получите быстро. :)
Более того, если хотелки сложные, то намного выгоднее чтобы её делала команда PVS-Studio, а не команда, разрабатывающая проект. Требуется погружение, тестирование, поддержка и т.д. А бизнес любит, когда делают нужный проект, а не развлекаются поделками (пусть и интересными :).
Более того, если хотелки сложные, то намного выгоднее чтобы её делала команда PVS-Studio
Обычно не сложные, а простые, но специфические для проекта (поэтому они никогда не попадут в апстрим и кодовую базу всего PVS).
А бизнес любит, когда делают нужный проект, а не развлекаются поделками (пусть и интересными :).
Это правда, но иногда эти хотелки экономят больше чем стоят.
if o == if !isAssigned { OP_intrinsiccall } else { OP_intrinsiccallassigned } {
....
}
Просто потому что приоритеты операторов логичны.
Хорошая цитата:
Conclusion: It's a mess. Why does C# do it this way? Because that's how C does it. Why? I give you the words of the late designer of C, Dennis Ritchie:
In retrospect it would have been better to go ahead and change the precedence of & to higher than ==, but it seemed safer just to split & and && without moving & past an existing operator. (After all, we had several hundred kilobytes of source code, and maybe [three] installations....)
Ritchie's wry remark illustrates the lesson. To avoid the cost of fixing a few thousand lines of code on a handful of machines, we ended up with this design error repeated in many successor languages that now have a corpus of who-knows-how-many billion lines of code. If you're going to make a backward-compatibility-breaking change, no time is better than now; things will be worse in the future.
Приоритеты в сишке плохие, но многие последующие языки следуют им просто чтобы быть «такими же».
Хорошо, что некоторые языки пересмотрели это в пользу большей логичности, не оглядываясь на то «как там в си».
С M_PI есть только одна проблема — это необязательная константа, некоторые компиляторы ее не предоставляют. Поэтому даже в коде STL можно увидеть захардкоженное число пи:
template <class _RealType>
template<class _URNG>
inline _LIBCPP_INLINE_VISIBILITY
_RealType
cauchy_distribution<_RealType>::operator()(_URNG& __g, const param_type& __p)
{
uniform_real_distribution<result_type> __gen;
// purposefully let tan arg get as close to pi/2 as it wants, tan will return a finite
return __p.a() + __p.b() * _VSTD::tan(3.1415926535897932384626433832795 * __gen(__g));
}
Так что пока не приедет С++20 с P0631, придется keep calm and constexpr auto pi = acos(1);
.
Чем компилировали? gcc 9.2 вроде может https://godbolt.org/z/E8OtfX
Status(): Mask(0), Mode(0){};
Или запрос для поисковика хотя бы дайте.
Топ 10 ошибок в проектах C++ за 2019 год