Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Пример проявления неопределённого повед…

Пример проявления неопределённого поведения из-за отсутствия return

10 Фев 2022

Рассмотрим практический пример, когда отсутствие оператора return в функции, возвращающей значение, приводит к неопределённому поведению. Отличная демонстрация того, как ломается неправильный код, который благодаря везению мог ранее успешно работать многие годы.

0917_undefined_behaviour_and_return_ru/image1.png

Мы рассматриваем паттерн ошибки, который стандарт кодирования SEI CERT C++ описывает так: MSC52-CPP. Value-returning functions must return a value from all exit paths.

The C++ Standard, [stmt.return], paragraph 2 [ISO/IEC 14882-2014], states the following: 

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

Простой пример кода с ошибкой:

int foo(T a, T b)
{
  if (a < b)
    return -1;
  else if (a > b)
    return 1;
}

Программист забыл написать return 0, если два значения равны. Не все ветви выполнения возвращают значение, и это приводит к неопределённому поведению программы.

В общем-то думаю, всё понятно. Достаточно известный и распространённый паттерн ошибки. Проверяя с помощью анализатора PVS-Studio открытые проекты, мы регулярно находим благодаря диагностике V591 ошибки этого типа: примеры.

Если всё понятно и ошибки ищутся, то зачем, собственно, эта заметка? Вот тут мы подошли к самому интересному!

Дело в том, что программисты постоянно интерпретируют неопределённое поведение более узко, чем оно есть на самом деле. Неопределённое поведение при забытом операторе return часто воспринимается так: функция вернёт любое случайное значение. Более того, предыдущий опыт программиста может подтверждать, что это именно так.

Нет! Неправильно! Неопределённое — значит неопределённое. Нельзя предположить, что будет. Код, который работал одним образом, может начать работать по-иному.

Чтобы это продемонстрировать, я приведу немного отредактированную дискуссию (RU) с сайта RSDN.

Название темы: забавный крэш

Linux, libc-2.33, GCC 11.1.0, оптимизация -O2, следующий код падает с SIGSEGV:

#include <string>
#include <iostream>

bool foobar(const std::string &s)
{
    std::string sx = s;
    std::cout << sx << std::endl;
}

int main(int argc, char **argv)
{
    foobar(argv[0]);
    return 0;
}

/home/user/test$ g++ -O2 -std=c++11 ./test.cpp -o ./test && ./test

./test.cpp: In function 'bool foobar(const string&)':

./test.cpp:8:1: warning: no return statement in function returning non-void [-Wreturn-type]

8 | }

| ^

./test

Segmentation fault (core dumped)

А если поменять bool foobar на void foobar или добавить return false, то не падает.

Не падает и при использовании старого компилятора – GCC 7.5.0.

Кстати, std::string, как выяснилось, никак не влияет на ситуацию. Аналог этого кода на C также падает, будучи собранным g++.

#include <stdio.h>

bool foobar(const char *s)
{
    printf("foobar(%s)\n", s);
}

int main(int argc, char **argv)
{
    foobar(argv[0]);
    return 0;
}

/home/user/test$ g++ -O2 ./test.c -o ./test && ./test

./test.c: In function 'int foobar(const char*)':

./test.c:6:1: warning: no return statement in function returning non-void [-Wreturn-type]

6 | }

| ^

foobar(./test)

Segmentation fault (core dumped)

Если так: gcc -O2 ./test.c -o ./test && ./test, то всё хорошо.

Компилятор просто не генерирует инструкцию возврата из функции (ret)!

0000000000001150 <_Z6foobarPKc>:
 1150:  48 89 fe              mov   rsi,rdi
 1153:  48 83 ec 08           sub   rsp,0x8
 1157:  48 8d 3d a6 0e 00 00  lea   rdi,[rip+0xea6]  # 2004 <_IO_stdin_used+0x4>
 115e:  31 c0                 xor   eax,eax
 1160:  e8 cb fe ff ff        call  1030 <printf@plt>
 1165:  66 2e 0f 1f 84 00 00 00 00 00   cs nop WORD PTR [rax+rax*1+0x0]
 116f:  90                    nop

0000000000001170 <__libc_csu_init>:
 1170:  f3 0f 1e fa           endbr64 
 1174:  41 57                 push  r15

Спасибо пользователю ononim с сайта RSDN за такой интересный пример.

Вот такое яркое непривычное проявление неопределенного поведения.

Какие выводы можно сделать из этого? На мой взгляд их два:

  • Не пытайтесь предсказать, к чему приводит неопределённое поведение. Если вам кажется, что вы, например, знаете, к чему приведёт переполнение знаковой переменной типа int, то это самообман. Может быть очень необычное проявление.
  • Код, вызывающий неопределённое поведение, может перестать работать в любой момент. Используйте предупреждения компилятора и инструменты статического анализа кода (например, PVS-Studio), чтобы найти и исправить подобные опасные фрагменты кода.

Дополнительные ссылки:

Популярные статьи по теме
Интервью с Джейсоном Тернером, одним из ведущих подкаста "CppCast": история и причины закрытия проекта

Дата: 27 Сен 2022

Автор: Ульяна Гришина

В этой статье мы поговорим с Джейсоном Тернером, одним из основателей CppCast. CppCast – это первый С++ подкаст, основанный С++ разработчиками. Начиная с 2015 года каждую неделю на CppCast выходили п…
Боремся с 16-летним легаси-кодом, или исправляем C и C++ front-end в PVS-Studio

Дата: 22 Сен 2022

Автор: Сергей Ларин

В 2022 году статическому анализатору PVS-Studio для языков C и C++ исполняется 16 лет. Если бы анализатор был человеком, то он бы уже заканчивал школу. Это очень старый проект, и система типов в нем …
Как фидбек помог улучшить наш C++ квиз

Дата: 31 Авг 2022

Автор: Алексей Саркисов

Ранее в нашем блоге мы рассказывали о квизе для C++ разработчиков. С момента запуска мы тщательно собирали обратную связь. Часть из неё касалась ошибок в работе квиза, которые мы естественно решили и…
Концепция умного указателя static_ptr<T> в C++

Дата: 30 Авг 2022

Автор: Гость

В C++ есть несколько "умных указателей" – 'std::unique_ptr', 'std::shared_ptr', 'std::weak_ptr'.
"Так исторически сложилось", или за что разделили V512

Дата: 12 Авг 2022

Автор: Михаил Гельвих

Как говорится, в любом деле самое сложное — это начать. Так и мы, очень долго откладывали разделение диагностики V512, но время пришло. Ну а о причинах и последствиях этого решения можно прочитать в …

Комментарии (0)

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно. Хотите узнать подробнее?
Принять