Comments 23
;Path to tf.exe. We should have a better solution, and Dev15 Bug 194740 is tracking this…
Мне кажется, что истинный «пафос» статьи в том, что либо реальные системные библиотеки Windows собираются не с теми заголовками, которые поставляются в составе публичного SDK, либо собираются со значимыми предупреждениями, если не с ошибками компиляции. Казалось бы, при чем здесь Лужков?! (С).
Примечание. Кстати, в срабатываниях на макросах анализатор часто формально прав. Но с практической точки зрения это никого не волнует и получается, что это всё равно ложные предупреждения :).
К сожалению, настроить анализатор недостаточно. Всё равно возникает ощущение, что многие срабатывания неверны. Иногда, когда начинаешь разбираться, это не так. Поэтому мои статьи наделены предостеречь программистов от поспешных выводов.
Плюс ещё бывают такие интересные ситуации, как описаны здесь. С практической точки зрения это ложное срабатывание анализатора, хотя инструмент и не виноват. Тут собственно никакой и морали-то нет. Просто захотелось рассказать про эту ситуацию. Хотя нет, вру. Есть мораль: мы оказываем глубокую качественную поддержку. Становитесь нашим клиентом, и мы поможем побороть любое зло.
Плюс ещё бывают такие интересные ситуации, как описаны здесь. С практической точки зрения это ложное срабатывание анализатор
Андрей, подскажите, а не может ли возникнуть ситуации когда компилятор что-то «наоптимизирует» на основе некорректных const qualifiers, что в итоге приведёт к некорректному «с практической точки зрения» исполнению кода? Я имею в виду «сложные» оптимизации, в т.ч. основанные на undefined behavior и т.п. если что...
Компилятор имеет право и может заменить это выражение на true.
const_cast снимает модификатор const со ссылки, но не позволяет писать в неё. Попытка сделать это приводит к undefined behavior.
Так что хоть я не смог заставить ни один компилятор на godbolt превратить эту проверку в true, полагаться на такое поведение компиляторов не стоит.
Если не менять объекты после const_cast, то тогда зачем вообще этот оператор нужен? :)
Даже разработчики компиляторов так делают. Вот первый попавшийся фрагмент кода из Clang:
static void getCLEnvVarOptions(
std::string &EnvValue, llvm::StringSaver &Saver,
SmallVectorImpl<const char *> &Opts)
{
llvm::cl::TokenizeWindowsCommandLine(EnvValue, Saver, Opts);
for (const char *Opt : Opts)
if (char *NumberSignPtr = const_cast<char *>(::strchr(Opt, '#')))
*NumberSignPtr = '=';
}
Ммм. Вы правы. Я неправильно интерпретиловал следующую цитату:
Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior.
А конкретно я упустил
using the resulting pointer or reference to write to an object that was declared const
Т.е. если у нас есть объект, который не объявлен как const, мы его скастовали в const, а потом кастом сняли const, в него можно писать и это не ошибка.
Так что кажется что ни в моём, ни в вашем примере UB нет.
Если функция получает объект по const ref, компилятор не знает как этот объект был объявлен. А значит компилятор не может предположить, что неконстантный объект, который мы передали по константной ссылке не может быть изменён.
Конкретно в данном случае излишняя интеллектуальность анализатора привела к тому, что из-за неправильного описания функции он начал выдавать ложное срабатывание.
А можно подробнее раскрыть утверждение: "Аргумент является указателем на константный объект. Получается, что с точки зрения анализатора функция GetNamedSecurityInfoW не может менять объект, на который ссылается указатель. "?
void foo(const int** pp){
const int* pi;
*pp = pi;
}
const int* p;
foo(&p);
Сигнатура функции гласит утрировано const T** p; Это же про константый объект, но не указатель на который указывает переменная. Откуда взялся вывод, что функция не может менять указатель, если указатель указывает на указатель, который не константый?
#define PINT int*
void f(const PINT * pi);
// препроцессируется в псевдокод
void f(const (int *) * pi);
// east const rules!!!
void f( int * const * pi);
Проверил мысль с учетом макроса и псевдонима. Поведение разное. Спасибо за наводку.
#define PINT int*
void foo(const PINT* pp){
*pp = nullptr;
}
typedef int* pint;
void foo(const pint* pp){
// *pp = nullptr;
}
int main(int argc, char** argv)
{
const int* p;
foo(&p);
int * const pc = NULL;
foo(&pc);
int* pi = NULL;
foo(&pi); // Case 2.
return 0;
}
В C квалификатор в передающемся по значению аргументе не учитывается и часто является причиной предупреждения компилятора.
В C++ (если мне не изменяет склероз) такой квалификатор не учитывается, но допускается.
В определении функций квалификаторы имеют смысл и в C, и в C++, поскольку в определении функции аргумент является определением локальной переменной. В этом случае квалификатор const «играет», хотя в C его смысл и отличается от C++ (тут константа просто константа, а не readonly переменная).
Если бы advapi32.dll компилировалась с теми заголовками, в которых аргументы квалифицированы, как const, то было бы как минимум предупреждение компилятора о несоответствии прототипа определению функции.
Как-то так,
Искренне Ваш КО)
typedef int near *PINT;
foo(const PINT *x);
В функцию передаётся указатель на константный объект. Значит, функция не может изменить объект, который пришёл в функцию из вне. В данном случае из вне приходит объект типа PACL (который на самом деле указатель). И вот этот объект (указатель) менять нельзя.Возможно дополнительные сведения помогли бы пользователю разобраться самостоятельно.
Статья на самом деле классная в частности именно тем, что напоминает о важности этого «секретного приемчика» в разработке.
Уже предвижу комментарий, что полезно показать путь вывода правила (как работал анализ потока данных). Т.е. из каких условий и операций следует, что получится ложь/истина/индекс за границами массива и т.д. Я работал с такими инструментами и, к сожалению, пришел к выводу, что часто путь не помогает, а только ещё больше усложняет картину.
Кстати, здесь бы и путь не помог. Вот переменная равна 0. Вот вызывается функция, которая не меняет эту переменную. Вот всегда истинное условие. То, что анализатор посчитал, что функция не меняет значение, это и так было понятно. Если же анализатор ещё начнёт отправлять ко всем объявлениям функций в системных заголовочных файлах… Человек тогда вообще потеряется в обилии данных и удалит анализатор :).
Что меня всегда удивляло, так это то, что в C T const * и T * — разные типы в списке аргументов прототипа функции, но не в возвращаемом значении.
Если я не прав, то пусть лучше знающие стандарты C и C++ меня поправят.
Ложные срабатывания в PVS-Studio: как глубока кроличья нора