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

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

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

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

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

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

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


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

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

Популярные статьи по теме
Под капотом SAST: как инструменты анализа кода ищут дефекты безопасности

Дата: 26 Янв 2023

Автор: Сергей Васильев

Сегодня речь о том, как SAST-решения ищут дефекты безопасности. Расскажу, как разные подходы к поиску потенциальных уязвимостей дополняют друг друга, зачем нужен каждый из них и как теория ложится на…
Ложные представления программистов о неопределённом поведении

Дата: 17 Янв 2023

Автор: Гость

Неопределённое поведение (UB) – непростая концепция в языках программирования и компиляторах. Я слышал много заблуждений в том, что гарантирует компилятор при наличии UB. Это печально, но неудивитель…
Топ-10 ошибок в C++ проектах за 2022 год

Дата: 29 Дек 2022

Автор: Владислав Столяров

Дело идёт к Новому году, а значит, самое время традиционно вспомнить десять самых интересных срабатываний, которые нашёл PVS-Studio в 2022 году.
PVS-Studio и RPCS3: лучшие предупреждения в один клик

Дата: 12 Дек 2022

Автор: Александр Куренев

Best Warnings — режим анализатора, оставляющий в окне вывода 10 лучших предупреждений. Мы предлагаем вам ознакомиться с обновлённым режимом Best Warnings на примере проверки проекта RPCS3.
Holy C++

Дата: 23 Ноя 2022

Автор: Гость

В этой статье постараюсь затронуть все вещи, которые можно без зазрения совести выкинуть из С++, не потеряв ничего (кроме боли), уменьшить стандарт, нагрузку на создателей компиляторов, студентов, из…

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

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