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

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

![0917_undefined_behaviour_and_return_ru/image1.png](https://import.viva64.com/docx/blog/0917_undefined_behaviour_and_return_ru/image1.png)

Мы рассматриваем паттерн ошибки, который стандарт кодирования SEI CERT C\+\+ описывает так: [MSC52\-CPP](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046336&src=breadcrumbs-parent)\. 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\.

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

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

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

В общем\-то думаю, всё понятно\. Достаточно известный и распространённый паттерн ошибки\. Проверяя с помощью анализатора PVS\-Studio открытые проекты, мы регулярно находим благодаря диагностике [V591](https://pvs-studio.ru/ru/docs/warnings/v591/) ошибки этого типа: [примеры](https://pvs-studio.ru/ru/blog/examples/v591/)\.

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

Дело в том, что программисты постоянно интерпретируют [неопределённое поведение](https://pvs-studio.ru/ru/blog/terms/0066/) более узко, чем оно есть на самом деле\. Неопределённое поведение при забытом операторе _return_ часто воспринимается так: функция вернёт любое случайное значение\. Более того, предыдущий опыт программиста может подтверждать, что это именно так\.

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

Чтобы это продемонстрировать, я приведу немного отредактированную [дискуссию](http://rsdn.org/forum/cpp.applied/8103918.flat) \(RU\) с сайта RSDN\.

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

Linux, libc\-2\.33, GCC 11\.1\.0, оптимизация \-O2, следующий код падает с SIGSEGV:

```cpp
#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\+\+\.

```cpp
#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_\)\!

```cpp
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](http://rsdn.org/account/info/69464) с сайта RSDN за такой интересный пример\.

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

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

1. Не пытайтесь предсказать, к чему приводит неопределённое поведение\. Если вам кажется, что вы, например, знаете, к чему приведёт переполнение знаковой переменной типа _int_, то это самообман\. Может быть очень [необычное проявление](https://pvs-studio.ru/ru/blog/posts/cpp/0374/)\.
1. Код, вызывающий неопределённое поведение, может перестать работать в любой момент\. Используйте предупреждения компилятора и инструменты статического анализа кода \(например, PVS\-Studio\), чтобы найти и исправить подобные опасные фрагменты кода\.

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

1. [Неопределённое поведение](https://pvs-studio.ru/ru/blog/terms/0066/)\.
1. [Неопределённое поведение и правда не определено](https://habr.com/ru/company/pvs-studio/blog/439502/)\.
1. [Разыменовывание нулевого указателя приводит к неопределённому поведению](https://pvs-studio.ru/ru/blog/posts/cpp/0306/)\.
1. [Undefined behavior ближе, чем вы думаете](https://pvs-studio.ru/ru/blog/posts/cpp/0374/)\.
1. [Неопределенное поведение, пронесенное сквозь года](https://pvs-studio.ru/ru/blog/posts/cpp/0883/)\.