C ++ возвращает локальную переменную из функции в качестве ссылки - PullRequest
0 голосов
/ 07 мая 2020

Мне было интересно, законно ли возвращать локальную переменную из функции (переменную, которая была создана в функции) в качестве ссылки. Мой лог c говорит мне, что это невозможно и вызывает ошибку компилятора. Однако мой компилятор думает иначе - он компилирует и выводит значение 10! (VS 2017) Если я попытаюсь скомпилировать его на онлайн-компиляторе, он выдаст ошибку сегментации.

Может ли кто-нибудь пролить свет на это? Если бы вас спросили, что будет делать этот фрагмент кода, вы бы сказали, что он печатает нормально или с ошибкой компилятора?

Вот код:

    #include <iostream>
    using namespace std;


    int &func(int x, int y) {
    int tmp = x > y ? x : y;
    return tmp;
    }

    void main() {
    int a = 3, b = 10;
    int &t = func(a, b);
    cout << t << endl;

}

Ответы [ 2 ]

1 голос
/ 07 мая 2020

Неопределенное поведение по существу определяется тем, что оно не приводит к легко диагностируемой ошибке. Считайте в https://en.cppreference.com/w/cpp/language/ub. Продвинутый компилятор может обнаружить его, но это не обязательно.
Это настоящая проблема UB - он действительно может делать именно то, что вы хотите. Пока этого не произойдет, что приведет к ошибкам, которые не срабатывают сначала, что делает их потенциально трудными для исправления, когда они, наконец, появляются.

Что компилятор может сделать с вашим фрагментом кода, это:

  • Память для tmp зарезервирована в стеке .
  • Создается ссылка. Давайте для простоты действовать так, как если бы это был обычный указатель. По сути, используется адрес этой памяти.
  • Возвращается ссылка.
  • Когда функция остается, все в стековой памяти, принадлежащей функции, освобождается. Это означает, что эти адреса памяти больше не зарезервированы, и другой процесс может зарезервировать их вместо этого.
  • Однако: нет необходимости тратить какое-либо время на какие-либо действия с содержимым памяти в этой позиции. Он по-прежнему содержит десять в целочисленном формате.
  • Ваша консольная печать обращается к памяти по адресу. Хотя он не зарезервирован, он все же содержит значение. Поскольку ваша переменная t объявлена ​​как целое число, она считывает эту часть памяти как единицу, а десять все еще там, все работает нормально.

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

Итак, во всем этом я много предполагал о том, как работает компилятор. Это не дано. Ваш вопрос
«Если бы вас спросили, что будет делать этот фрагмент кода, вы бы сказали, что он печатает нормально или с ошибкой компилятора?» однозначного ответа нет. Можно сказать, что он может делать то, что вы хотите, но это не дано.
Распространенная шутка заключается в том, что, поскольку компилятору разрешено делать все, что угодно , если код имеет неопределенное поведение, он также приводит к тому, что рой демонов выходит из вашего носа .

Это, конечно, преувеличено, но дело в том, что неопределенное поведение может делать многое, чего вы действительно не ожидаете .
Например: посмотрите на это: http://www.cpp.sh/32x7f. Там доступ к двумерному массиву осуществляется за пределами вывода консоли. Что происходит, когда вы его запускаете, так это то, что он продолжает итерацию, распечатывая переменную итератора с более чем сотней, в то время как for l oop должен был остановиться после трех. Я понятия не имею, что там происходит, доступ за пределы не должен иметь возможности связываться с l oop, но, видимо, это так. (Обратите внимание, что глобальная переменная в нем имеет плохой стиль, но не связана с поведением.)

Важно: научитесь распознавать неопределенное поведение и никогда не полагаться на его работу определенным образом.

0 голосов
/ 07 мая 2020

Вы никогда не return a переменную (технически l-значение или местоположение) в C ++. Вы возвращаете значение или местоположение (ссылка - это местоположение, мысленно подобная указателю).

Ваш сбивающий с толку func возвращает местоположение для временная вещь. Вероятно, это связано с каким-то неопределенным поведением.

Практически, включите предупреждения в вашем компиляторе. С недавним G CC, компиляция с g++ -Wall -Wextra -g

Спецификация C ++ 11 (читается n3337 ) в основном определяет поведение правильных программ C ++. При неопределенном поведении компилятор (или реализация C ++) может вызвать ядерную войну и по-прежнему соответствовать этой спецификации.

На практике разработчики компиляторов знают о теореме Райса , но попытайтесь дать, в некоторых случаях, полезные предупреждения пользователям. Вы, наверное, захотите их включить (конечно, в этом случае время компиляции увеличивается). Вы можете использовать Clang stati c анализатор или аналогичные инструменты, возможно, Frama- C или, в конце 2020 года, мой Bismon . в сочетании с G CC.

Вы можете расширить недавний G CC своим собственным плагином с дополнительными предупреждениями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...