Pull to refresh

Comments 61

UFO just landed and posted this here
Вы правы. С практической точки зрения, погрешность мала и наверняка не окажет влияние на что-то. Но ошибка есть ошибка. И если уж совсем быть точным, то программа:
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.
Дискуссия вышла немного бестолковая :). Дело в том, что в коде в числе PI на самом деле не одна ошибка, а две.
3.141592538   // В коде.
3.1415926538  // Я исправил 1 ошибку.
3.14159265358 // А их там две! Ещё и 5 перед 8 нет.

Я имел в виду, что эта вторая ошибка вообще уже никак не влияет на результат.
M_PI не является стандартом, например :)
UFO just landed and posted this here
Я что-то не понял 5-е место. char же знаковый и может быть равен EOF. Неясно, ожидается ли EOF на входе, но почему предлагается использовать int в качесвет фикса?
Да. Вот только беда, что и буква 'я' будет представлена как -1 (EOF).
Ну так вряд ли код должен взаимодействовать с какими-то произвольными кодировками, т.к. тогда бы он использовал UTF-8, так что неясно, что они вообще хотели сделать.
Может ли неправильный код работать правильно на ограниченных входных данных? Может. Но это не повод так писать :). Кто знает, как и где код будет использоваться в дальнейшем.

Какой-то выдуманный аргумент. Все программирование — это про соблюдение контрактов. Если в данной точке кода контракт вызываемой функции не подразумевает наличие чего-то произвольного, то этот код более чем валидный.

Просмотрел стандарт языка и не нашел чтобы EOF был строго равен -1. Говорится, что это просто отрицательное число. Скажем, -100500. А ведь есть еще WEOF, который даже отрицательным быть не обязан
Как обычно, передайте респект иллюстратору!
Вопрос про пример (2-ое место).
Если я правильно понимаю фактическая ошибка вызвана вот этой строкой:

TITLE(Book::tr("Logarithmic Base Conversion"))

То есть анализатор должен был сообщить о ней либо там где описывается реализация

TITLE

либо там где описывается

Book::tr(

я что-то упускаю?
Если честно, то я вообще не понял вопрос. :(
Вопрос в том в какой строке из примера ошибка?

И если она в строке TITLE, почему анализатор нашел ее именно тут, а не при анализе кода в другом месте где описывается функционал этого TITLE?
Какой функционал? Это макросы. Они раскрылись внутри функции makeAlgebraLogBaseConversionPage. Соответственно в функции makeAlgebraLogBaseConversionPage и найдена ошибка.
Я не Сишник.
Я имею в виду что если ошибка в макросе. То анализатор должен бы ругаться там где декларируется макрос. Нет?

Можете мне подсказать, что в этом фрагменте кода надо исправить чтобы он стал валидным?
Я имею в виду что если ошибка в макросе. То анализатор должен бы ругаться там где декларируется макрос. Нет?
Ругаться на содержимое макросов часто невозможно. Пока они не раскроются, неизвестно, верный получится код или нет. Но даже, если не верный, всё равно может быть виноват не сам макрос, а использующий его макрос/код. Или макросу может быть передан неправильный аргумент, на который макрос не рассчитан. Не знаю как просто объяснить.

В PVS-Studio есть диагностики на тему макросов (пример V1003). Но их мало. В основном работа всегда идёт с уже препроцессированных кодом.

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

Макросы — такая штука, что невозможно сказать, валидный он или нет (за исключением синтаксиса самого определения).


Например


#define MY_MACRO while(somevar) doBlaBla()

И что теперь? Если somevar и doBlaBla определены в месте вызова, то всё пойдёт как по маслу. А если нет, то возникнет ошибка.
А ещё если макрос не используется вообще, то в нём хоть синтаксически бред будет — компилятору пофиг. Этакий код Шрёдингера — пока не вызовешь, ошибка и есть, и нет.


Поэтому в плюсах от макросов и избавляются всеми силами (благо можно). В си с этим хуже.


P.S.: при всём том, анализатору бы было неплохо показать раскрытый макрос для ясности происходящего.

Спасибо за статью.


Что любопытно, что в современных языках половина ошибок была бы отловлена на этапе компиляции
(9 8 6 5 3)

C# — «современный» язык? Сравнение того же char с -1 в нем точно так же не отлавливается на этапе компиляции, насколько я вижу. Точно так же, если сделать переменную int EOF = -1 и сравнить char с ней, компилятор даже не пикнет. Или «современные языки» — это конкретно тот самый, где implicit conversions принципиально запрещены, а остальные недостаточно современны? :)

Нет, C# не современный. Современные — Го, Раст, Свифт, Идрис и прочие. Появившиеся в последние 10 лет.


И даже го конечно пропускает часть из этих проблем, но не все (раз, два, 9 записать невозможно, остальное не проверял).




А про C#: он хоть и старенький (20 лет это приличный срок), у него есть секретное оружие — рослин и его анализаторы. Примерно как PVS, только если вас бесит какая-то проблема вы можете самостоятельно за пару часов напилить анализ который будет проверяться компилятором, и не придется ждать пока ребята из PVS решат что стоит такую проверку запилить. Буквально: сделали репу, написали что анализировать и должно ли это быть ворнингом/ошибкой, опционально запилили фикс, запушили пакет, добавили себе в проект, нашли все проблемные места. С момента как захотелось зафиксить какую-то фигню до момента как проект для сборки потребовал все такие места починить — буквально час-два.

Ну Идрис, положим, появился таки больше 10 лет назад. Да и Go, скажем так, уже не совсем укладывается :) Но ОК, пусть будет такое деление :)
Для C++ тоже есть clang analyzer с его API, к нему можно писать свои плагины для проверки того, что тебе надо ad hoc. Тут где-то не так давно даже статья пробегала про это. Думаю, набив руку, это тоже можно делать довольно быстро.

А какой процент разработчиков сидит на 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

Понял. Любопытно, спасибо. Хотя юзабилити немного страдает.

Ну типичные задачи:


  1. как запускать анализаторы a, b, c, d автоматически при билде любого проекта в IDE
  2. как запускать анализаторы x, y, z только при билде проектов Y и Z
  3. как хранить настрйки анализаторов в репозитории чтобы при клонировании репы пользователь автоматически конфигурировал локальную сборку с соответствующими анализаторами (аналогично, как .gitattributes конфигурирует локальные настройки гита для конкретного репозитория).
Ну это такие уже вопросы… скажем так, рутинного, а не принципиального характера. С той или иной степенью удобства, думаю, их можно решить практически везде, где-то более удобно, где-то менее. Зависит от IDE и системы сборки, их же много разных.
На самом деле пользователи анализатора часто предлагают нам сделать разные диагностики. И обычно мы реализуем их

Не спорю, но сколько пользователям эту диагностику ждать? Месяц? Два? Полгода? Плюс у вас же свои приоритеты, тогда я свою хотелку не получу никогда.


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


Всё же это совсем разные сроки.


И наконец, как я чуть выше написал, некоторые аналитики имеют смысла в разрезе конкретного проекта. Кстати, пример не надуманный, у меня на одном проекте была именно такая проверка, что компоненты написанные для одного клиента не используются в компонентах другого клиента. Это приводило к ошибке компиляции и нужно было с этим явно что-то делать, например, рефакторить компонент и выносить его в Common проект.

Не спорю, но сколько пользователям эту диагностику ждать? Месяц? Два? Полгода? Плюс у вас же свои приоритеты, тогда я свою хотелку не получу никогда.
Становитесь клиентом и получите быстро. :)

Более того, если хотелки сложные, то намного выгоднее чтобы её делала команда PVS-Studio, а не команда, разрабатывающая проект. Требуется погружение, тестирование, поддержка и т.д. А бизнес любит, когда делают нужный проект, а не развлекаются поделками (пусть и интересными :).
Более того, если хотелки сложные, то намного выгоднее чтобы её делала команда PVS-Studio

Обычно не сложные, а простые, но специфические для проекта (поэтому они никогда не попадут в апстрим и кодовую базу всего PVS).


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

Это правда, но иногда эти хотелки экономят больше чем стоят.

Все так. Делфи большую часть того, что PVS ловит, просто не скомпилирует. Нужен плюсам костыль в виде PVS, что поделать.
Конечно, не скомпилирует. Ведь в Дельфи банально нет ни тернарного оператора, ни приватного наследования, если уж речь об этой статье. Это все равно, что сравнивать игрушечную машинку с настоящей и говорить «а вот игрушечная не задавит» :)
Вот ваша 'банальщина' по коду боком и вылазит. В Go, Rust вот тоже нет тернара, и наследования. Поняли, что выстрелы в ногу — тупиковый путь.
«Потому, что не нужно» ©? :) Не перегружайте голову, переходите сразу на брейнфак. Там всего 8 операторов на все про все, и больше ничего — остальное «не нужно». Запутаться решительно невозможно.
В расте точно так же инлайн можно написать, просто это проблем не вызовет:

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.


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

Хорошо, что некоторые языки пересмотрели это в пользу большей логичности, не оглядываясь на то «как там в си».
У раста в этом плане своих приколов хватает. Например, с выражениями типа "..1 == var". На первый взгляд может показаться, что это сравнение ренджа со значением переменной var, но на самом деле это RangeTo<bool> и вычисляется как "..(1 == var)". Сюрприз-сюрприз. То же самое с выражениями типа «var == 1..», только там будет RangeFrom<bool>. Как грицца, good operator precedence is impossible, так что его нужно просто знать, вот и все.

Действительно, довольно неудачная конструкция. Хотя понятно, почему приоритеты так сделаны: чтобы можно было писать a..b+1 без скобочек.


Спасибо за пример.

С M_PI есть только одна проблема — это необязательная константа, некоторые компиляторы ее не предоставляют. Поэтому даже в коде STL можно увидеть захардкоженное число пи:


вроде бы libc++
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);.

error: constexpr variable 'pi' must be initialized by a constant expression
А clang 9 нет. И судя по всему по стандарту не должно компилироваться.

Ле вздох. Ну, значит просто const.

Вроде как предложение о constexpr <cmath> в С++20 не прошло, печаль.

Но вроде math constants все-таки будут.

Расширение. Я бы не полагался. Передайте -fno-builtin.
Добрые люди, помогите понять, что означает выражение из 7-го места:
Status(): Mask(0), Mode(0){};
Или запрос для поисковика хотя бы дайте.
Это список инициализации конструктора по умолчанию
Когда в коде будет написано:
Status tmp;

То будет вызван этот конструктор, который инициализирует поля Mask и Mode значением 0
По поводу Третье место: «Неуловимое исключение». Всегда был уверен, что любые ошибки приватного наследования обнаруживаются на стадии компиляции. А тут такое. Проверил в MSVS. Если поставить в конце catch(…), то catch(std::exception&) молча пропускается, перехват в catch(…). Если убрать catch(…), то в debug возникает abort, в releast ничего. Стало быть полная статическая типизация при перехвате исключения невозможна. Очень полезная информация.
Sign up to leave a comment.