Для получения триального ключа
заполните форму ниже
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), чтобы найти и исправить подобные опасные фрагменты кода.

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

Популярные статьи по теме
Как различить C и C++ разработчиков по их коду

Дата: 12 Май 2022

Автор: Гость

Так уж случилось, что я пишу код для разных IoT-железок, связанных с электричеством, типа зарядных станций автомобилей. Поскольку аппаратных ресурсов, как правило, вполне достаточно, то основным фоку…
Отладочный вывод на микроконтроллерах: как Concepts и Ranges отправили мой printf на покой

Дата: 06 Май 2022

Автор: Гость

Здравствуйте! Меня зовут Александр, и я работаю программистом микроконтроллеров.
Нереальный baselining или доработки PVS-Studio для Unreal Engine проектов

Дата: 26 Апр 2022

Автор: Валерий Комаров

Статический анализатор PVS-Studio постоянно развивается: улучшаются различные механизмы, происходит интеграция с игровыми движками, IDE, CI/CD и другими системами и сервисами. Благодаря этому несколь…
Разбор некоторых вредных советов для С++ программиста

Дата: 21 Апр 2022

Автор: Андрей Карпов

Юмор юмором, но осторожность не повредит. Вдруг кому-то не до конца понятно, почему какой-то из советов является вредным. Здесь можно найти соответствующие пояснения.
Четыре причины проверять, что вернула функция malloc

Дата: 20 Апр 2022

Автор: Андрей Карпов

Некоторые разработчики пренебрежительно относятся к проверкам: удалось ли выделить память с помощью функции malloc или нет. Их логика проста – памяти всегда должно хватить. А если не хватит, всё равн…

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

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