Comments 44
А сколько всего было просмотрено пулл реквестов? Если каждый просмотренный содержал ошибки, которые находил анализатор, то это хорошо. Если же пришлось просматривать множество фиксов, то совершенно другое. Сложно говорить о полезности инструмента для фикса критичных багов без такой информации.
Но для совсем честного понимания выгоды от использования PVS случаи
не тот цвет кнопки, поправили логику работы меню, использовали не ту формулутоже в общем-то надо учитывать.
Плюсом, Lint делает еще замечания, типа бест практис, что так лучше не делать, но ничего страшного…
А можно как то получить временно на месяц, сравнить с PC Lint.Можно. Напишите нам и мы выдадим Вам ключ на месяц.
PC Lint, я так понял полноценный компилятор почти… т. е он проверяет исходный код и не нуждается в наличии стороннего компилятора.Так это… PVS-Studio тоже почти полноценный компилятор (по разбору кода). Внешние компиляторы используются только для препроцессирования. Препроцессировали внешним компилятором, сами построили дерево разбора и давай применять разные технологии анализа :).
P.S. Почему мы сами не пишем о сравнении PVS-Studio с другими статическими анализаторами кода: www.viva64.com/ru/b/0637
Всегда с интересом читаю ваши статьи.
Но в данном случае мне непонятно одно… Почему стандарт языка, а за ним все следом говорят о том, что разыменование указателя с адресом 0 является UB? Ноль — это всего лишь адрес памяти к которому так же можно "достучаться" (я даже доказал это на одном известном форуме).
С таким же успехом можно сказать про любой указатель в любой программе.
Дело в том, что разработчики стандарта языка просто приняли за должное считать nullptr невалидным адресом. Но почему-то они не учли, что на языке C++ можно писать без конкретной ОС. Вообще без ОС, где по нулевому адресу (например на архитектуре x86) находится таблица векторов прерываний.
Хотелось бы услышать ваше мнение по этому поводу.
Глубинный смысл именно в защите памяти от ошибок работы с указателями. Ну как в этой статье зевнули *. Лучше упасть так, чем запороть данные и дальше неизбежно пойти в разнос.
По факту в большинстве взрослых архитектур и ос (16 битный дос не берем) первые 64к адресного пространства процессов огорожены или вообще не выделены, т.е. любое обращение к ним даст segfault.По какому-такому факту? Кто вам сказал этот бред? Что значит «не выделены»? Кто их должен «выделять»?
Я легко могу обратиться к адресу ниже 64к на x86 без ОС.
И да, разделяйте понятия «архитектура» и «ОС».
Кстати, процессор архитектуры x86 изначально запускается в 16-битном режиме, а уж после (по требованию загрузчика или ОС) переходит в защещенный.
Кстати, тут недавно все спорили насчет &p->element. Дак вот, я считаю, что стандарт об это ничего не говорит вообще. Это такое крупное пятно.
Но давайте всё-таки определим несколько вещей. Вы понимаете, что по факту нет никакого оператора ->? Это просто shorthand для (*p).element (скобки обязательны, поэтому и shorthand). Очевидно, что это туфта какая-то для NULL. Теперь добавим &. Оно раскрывается в &((*p).element). Обратите внимание на еще одни скобки. Понимаете, вы берете & от element! Но структуры то нет… В общем это как-то не очень само по себе. На самом деле все еще сложнее…
Теперь про offsetof. Wikipedia уже исправили.
en.wikipedia.org/wiki/Offsetof
«It has generated some debate if this is undefined behavior according to the C standard, since it appears to involve a dereference of a null pointer (although, according to the standard, section 6.6 Constant Expressions, Paragraph 9, the value of the object is not accessed by the operation).
И кстати, когда вы искали на stackechange, слона то вы и не заметили, там такое в одном месте есть…
stackoverflow.com/questions/28482809/c-access-static-members-using-null-pointer/28483477
Дак вот, я считаю, что стандарт об это ничего не говорит вообще.
Разве в C99 нет специального параграфа именно по поводу суперпозиции &
и *
?
C99, §6.5.3.2
The unary&
operator yields the address of its operand.
…
If the operand is the result of a unary*
operator,
neither that operator nor the&
operator is evaluated and the result is as if both were
omitted, except that the constraints on the operators still apply and the result is not an
lvalue.
Даже больше, есть специальная сноска, явно определяющая &*NULL
как корректную операцию:
C99, footnote 87
Thus,&*E
is equivalent toE
(even ifE
is a null pointer), and&(E1[E2])
to((E1)+(E2))
.
Дак вот, я считаю, что стандарт об это ничего не говорит вообще.
Разве в C99 нет специального параграфа именно по поводу суперпозиции & и *?
Так тут, насколько я понимаю, явной суперпозиции нет (в случае &(p->element)
). Порядок операций-то такой — dereference, обращение к полю, получение адреса, т.е. &
и *
идут не подряд, и "свернуть" их так, как указано в цитате, нельзя.
Разыменование нулевого указателя не является undefined behavior.Согласно стандарту — является. В последнем черновике не удалось беглым взглядом найти это утверждение. Приведу другой источник.
Само по себе разыменовывание 0 теоретически означает разыменовывание 0 адреса...Так я об этом и писал.
А и segfault — это ядро ругается, и это не часть стадарта.Про segfault я ничего и не говорил. Это к товарищу vanxant.
В ембеде да, такое встречается. Но там у них вообще, главный кукбук — еррата на конкретный чип.
Вы можете ответить хотя бы на один из моих вопросов?
Отвечать на поток вопросов в стиле «кто вам сказал этот бред» я не намерен.
Отвечать на поток вопросов в стиле «кто вам сказал этот бред» я не намерен.Ну ответьте хотя-бы на не бредовые вопросы.
Я Вас чем-то обидел?
Почему разыменование nullptr это UB? Я уже ответил: потому что в большинстве ОС это может привести к краху процесса. Но в каких-то случаях (например, системах без MMU) — может и не привести. При этом в начале адресного пространства процесса обычно ОСи хранят всякие системные таблицы — про память, права, дескрипторы и т.д, лазить в которые как минимум опасно и может привести к непредсказуемым эффектам. Так что это именно undefined, а не unspecified behaviour.
Это не "наезд", не обижайтесь.
Вот только нулевой указатель (nullptr
) и указатель на нулевой адрес памяти (reinterpret_cast<T*>((int)0)
) — это разные значения, которые могут "случайно" совпасть в рантайме.
Почему стандарт языка, а за ним все следом говорят о том, что разыменование указателя с адресом 0 является UB?
Прежде всего, неопределенным поведением является разыменование именно некорректного указателя, а не указателя с адресом 0
:
C99, footnote 87
Among the invalid values for dereferencing a pointer by the unary*
operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.
При этом под нулевым указателем понимается не указатель с адресом 0
, а некоторая константа NULL
, или nullptr
с внутренним представлением, которое вполне может оказаться не 0
. Другими словами, для корректного указателя ptr
верно ptr != NULL
, даже если (uintptr_t) ptr == 0
.
C99, §6.3.2.3
An integer constant expression with the value0
, or such an expression cast to typevoid *
, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
P.S. Занимательно, что в таком случае указатели ptr1
и ptr2
uintptr_t zero = 0;
void
*ptr1 = (void *) 0, /* == NULL */
*ptr2 = (void *) zero; /* != NULL */
могут оказаться не равны, потому что zero
для ptr2
не является integer constant expression.
Именно, что на разных архитектурах будет разное поведение.
И поэтому в общем оно не определено. Когда вы программируете embedded-систему, где это легальный адрес, поведение будет тоже вполне определённым. Но тут речь о стандарте всего языка, а не только в применении к микроконтроллерам. Именно поэтому оно и называется прямо, как есть — undefined.
(к слову, не так досаждает попытка доступа по нулевому адресу, как аналогичная по околонулевому. Когда указатель на инстанс класса нулевой, но при этом конкретное поле уже имеет другое (ненулевое) значение. Например, 0x10. Проверку на "не-нуль" оно проходит, но при использовании это всё равно UB).
То, что вы предлагаете называть undefined, в стандарте называется unspecified. Код, содержащий undefined behaviour, не может быть корректным в частном случае, в отличие от кода, содержащего unspecified behaviour. Вы согласны с тем, что следующий код не содержит UB?
void foo(uintptr_t offset) {
char *ptr = (char *) offset;
char val = *ptr;
}
foo(0);
От компилятора зависит, как именно offset
будет приведен к ptr
.
От архитектуры и операционной системы зависит, что именно случится при чтении по адресу ptr
.
На мой взгляд, здесь нет неопределенного поведения с точки зрения языковой грамматики.
Если PVS-Studio столкнётся с системой, где надо писать по нулевому указателю...А есть такая вероятность?)
Мы регулярно проверяем наш статический анализатор самим собой.
Во-первых, у нас используется инкрементальный анализ. То есть анализатор автоматически стартует на файлах, которые только что были перекомпилированы. И ошибки (которые обнаруживаются анализатором) если и появляются в нашем коде, то сразу же исправляются. При этом они не попадают ни в систему контроля версий, ни в баг-трекер.
Во-вторых, у нас настроен автоматический запуск анализатора каждую ночь на свои исходники. Поэтому даже если результаты инкрементального анализа кто-то просмотрел, то разработчикам приходят по почте отчеты об найденных и не устранённых ошибках.
В итоге, хотя мы и регулярно проверяем свой код, мы никогда не сможем написать статью об ошибках, найденных с помощью нашего анализатора в нем самом.
НО! Кое-что есть: Проверяем исходный код плагина PVS-Studio с помощью PVS-Studio.
Вот этого вывода я не понял.
Если, например, новой эвристикой вы находите что-то, что раньше не находилось, почему бы не написать об этом на примере собственного кода?
Да, сбор данных будет не одномоментный, а растянутый во времени, но всё равно может набраться достаточно интересных примеров.
Мне казалось, что GCC вполне себе выкидывал предупреждения (хотя, давно не писал на С++, может и ошибаюсь) и исправить можно было и без анализатора. Тогда тут разработчики сами виноваты, что предупреждения не читают (еще, как вариант: у них столько предупреждений при компиляции, что они на них уже не обращают внимания).
язык замечательный, но лажа вокруг него…
начиная от помощника IDE который невероятно хуже чем в Java
у вас идей нет сделать кросс -языковой вариант (просто по набору правил, тогда расширить можно для всего) или вариант чтобы юзем мог програмно его вызывать в качестве макро движка было бы круто
Нет. Если уж делать, то полноценный анализатор.
У нас есть вот такая база: "Ошибки, обнаруженные в Open Source проектах разработчиками PVS-Studio с помощью статического анализа".
А то, что вы пишите именно так и будет восприниматься.
Consider inspecting the statement of '*pointer++' pattern. Probably meant: '(*pointer)++'.
обычный инкремент поинтера
The 'first' argument of 'Foo' function is equal to the 'second' argument.
тоже часто бывает в векторных операциях и т.п, ничего криминального
A suspicious expression 'A[B < C]'. Probably meant 'A[B] < C'.
А это с какой стати вообще?
обычный инкремент поинтера
Только лично мне, например, непонятно, зачем инкрементировать указатель и в той же самой операции его разыменовывать, особенно если результат разыменования ничему не присваивается (как обычно бывает в случае, если задумывалось инкрементировать значение по указателю).
тоже часто бывает в векторных операциях и т.п, ничего криминального
Читайте внимательнее:
Передача одного и того же значения в качестве двух аргументов для многих функций является нормальной ситуацией. Но если речь идет о таких функциях как memmove, memcpy, strstr, strncmp, то это очень подозрительная ситуация.
А это с какой стати вообще?
А Вы умеете индексировать массивы boolean-ами, да ещё и так, что код получается понятным, читабельным и поддерживаемым?
просто
просто???
как_нарисовать_сову.jpg
Ошибки, которые не находит статический анализ кода, потому что он не используется