Как стать автором
Обновить

Работа с возражениями: статический анализ будет отнимать часть рабочего времени

Время на прочтение6 мин
Количество просмотров5.6K
Всего голосов 40: ↑34 и ↓6+28
Комментарии18

Комментарии 18

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


Ситуация совершенно аналогичная мониторингу. Чем больше красных лампочек надо игнорировать, тем ниже его польза. См ядерную аварию на 3 miles island, где была именно ситуация "100 лампочек красные и их надо игнорировать, 101ая — признак аварии реактора".

Я считаю, что система, в которой варнинги надо «подавлять» является деструктивной для производственной культуры.
Вроде как мысль правильная и я с ней согласен. Но только она мимо :). Вот возьмём, например, проверки ошибок в Microsoft Word. Он тоже иногда подчёркивает не то, что нужно. Повод ли это отключить проверку текста? Нет конечно. С ней, текст будет более качественный, несмотря на ложные срабатывания.

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

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


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

У меня ощущение, что статический анализатор для С/С++ — это как попытка заложить прорвавшуюся плотину мешками с песком.


UB без варнингов в стандарте языка — это всё. Можно закапывать. Никакие сторонние иструменты эту проблему уже не решат.

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


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

UB — это неотъемлемая часть современной действительности, смиритесь :) В современных CPU как таковых полно UB. Например, на x86 попытка выполнить инструкцию BSF над нулевым source operand — это UB в том смысле, что содержимое destination operand может оказаться абсолютно любым, и если рассчитывать на его содержимое в дальнейшем потоке выполнения, то в конечном счете может произойти что угодно. В AVR попытка выполнить любую из следующих инструкций:

LD r26, X+
LD r27, X+
LD r26, -X
LD r27, -X

— тоже аналогично является UB. C/C++, как языки, максимально приближенные к «железу», и максимально оптимизируемые, используют UB в своих целях, рассчитывая на то, что в корректно написанной программе не нужно будет делать на каждый чих кучу неявных runtime-проверок на NULL, на переполнение знаковых типов, на выход за границы массива, и так далее, и тому подобное. Варнинги тут не помогут просто потому, что компилятор не способен отловить такие ситуации на этапе собственно компиляции — как, например, отловить signed overflow на этапе компиляции? Да никак — только runtime checks, только хардкор. C/C++ предполагают, что разработчик позаботится об этом сам в тех местах и в той степени, в которой посчитает нужным.
Например, на x86 попытка выполнить инструкцию BSF над нулевым source operand — это UB в том смысле, что содержимое destination operand может оказаться абсолютно любым

Нет, это не undefined behavior, а всего лишь unspecified. Undefined behavior тут бы бы, если бы некорректная команда, к примеру, повреждала память микропрограмм. Ну или одновременное обращение к одной и той же области памяти из разных ядер без барьеров — но это "привычное" UB, его обычно в недостатки языка не записывают.

Unspecified behavior в терминах C/C++ в данном случае был бы, если бы, скажем, в зависимости от конкретной модели процессора значение destination operand было бы разным, но отнюдь не произвольным (each unspecified behavior results in one of a set of valid results). То есть, например, unspecified behavior — это операция foo() + boo(), результат которой в конце концов строго определён (результат foo() плюс результат boo()), но в каком порядке будут вызваны foo и boo — не определено. Документация Intel же никакого set of valid results не очерчивает.

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


UB — это когда, например, выражение i >= 0 ? i : 0 выдаёт -1.

Ну так и в вашем случае будет "хоть какое-то значение, которое можно обработать" :) Главное отличие unspecified behavior от undefined ведь именно что в наличии описанного в документации set of valid results. Ну это опять же в терминах C/C++ если говорить, в документации от Intel своя терминология (там, кстати, прямо употребляется термин undefined).

Ну и какое значение в моём примере у переменной i?

Ну у вашего выражения есть конкретный результат (-1), который можно обработать :) В общем, ладно, над вопросами перекрестного применения терминологии из разных областей можно спорить бесконечно: я считаю, что в случае BSF undefined behaviour потому, что отсутствует документированный set of valid results, а вы, очевидно, считаете, что это unspecified behaviour потому, что в регистре в конце концов будет что-то, что укладывается в его размерность (хоть это что-то и нельзя будет потом использовать каким-либо разумным образом). Пусть каждый останется при своем мнении :)
Ну и какое значение в моём примере у переменной i?
unsigned i = 0xFFFFFFFF;
int result = i >= 0 ? i : 0;

Это вы написали вариант, при котором в самом выражении i >= 0 ? i : 0 происходит UB. А я писал про вариант, когда UB уже произошло до этой строчки.


Например, что-то вроде такого:


int foo = внешняяФункцияЧтоВернётIntMax();
if (foo < 0) {
    return;
}

int i = foo*2 + 1;
if (i >= 0) {
    printf("%d >= 0", i); // -1 >= 0
}

Можно ли считать, что в переменной i тут вообще находится хоть какое-то значение?

Здесь вы смешиваете контексты. Когда говорите «значение, которое, несмотря на бессмысленность, все же можно обработать» — это я понял как о скомпилированном коде (runtime). Там всё детерминированно, а пример выше — об оптимизациях компилятора. В compile-time никакого значения ещё нет.
это я понял как о скомпилированном коде (runtime). Там всё детерминированно

Так именно об этом я и говорю! В рантайме всё детерминировано (по крайней мере, на одном ядре). Некорректно говорить, что какая-то там инструкция процессора приводит к UB в том смысле, который вкладывается в это понятие языком C++ (по тех пор, пока эта инструкция не портит железо).

Чисто теоретически, в инструкциях процессора тоже может быть UB. Например, значение считывается с какой-то внутренней шины, а недопустимый режим адресации в инструкции подключает к этой шине сразу два или более выхода. И тут — кто кого перетянет, на разных чипах может даже быть по-разному.
Аналогия понятная, но некорректная. В случае «100 лампочек красные» человек легко запутается, какие лампочки надо замечать, а какие нет. В случае предупреждений анализатора с корректно заглушёнными проблема только тогда, когда они вдруг могут перейти из неважных в важные, не меняя положения в коде и содержания предупреждения. По-моему, это очень малореально.
Кстати, если бы на том атомном блоке постоянно горящие красные лампочки закрывали заглушками — было бы аналогично: нормально не видим красных, одна появилась — ой, надо что-то делать.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий