Подтвердить что ты не робот

Почему компиляторы дают предупреждение о возврате ссылки на локальную переменную стека, если это поведение undefined?

В стандарте С++ утверждается, что возвращение ссылки на локальную переменную (в стеке) - это поведение undefined, поэтому почему многие (если не все) из существующих компиляторов дают предупреждение только для этого?

struct A{
};

A& foo()
{
    A a;
    return a; //gcc and VS2008 both give this a warning, but not a compiler error
}

Не лучше ли, если компиляторы выдадут ошибку вместо предупреждения для этого кода?

Есть ли большие преимущества, позволяющие компилировать этот код с помощью всего лишь предупреждения?

Обратите внимание, что это не ссылка const, которая может продлить время жизни временного ресурса самой ссылки.

4b9b3361

Ответ 1

С точки зрения компилятора почти невозможно проверить, возвращаете ли вы ссылку на временную. Если бы стандарт диктовал, что для того, чтобы быть диагностированным как ошибка, написать компилятор было бы почти невозможно. Рассмотрим:

bool not_so_random() { return true; }
int& foo( int x ) {
   static int s = 10;
   int *p = &s;
   if ( !not_so_random() ) {
      p = &x;
   }
   return *p;
}

Вышеуказанная программа правильная и безопасная для запуска, в нашей текущей реализации гарантируется, что foo вернет ссылку на переменную static, которая является безопасной. Но с точки зрения компилятора (и с отдельной компиляцией, где реализация not_so_random() недоступна, компилятор не может знать, что программа хорошо сформирована.

Это пример игрушки, но вы можете представить себе похожий код с разными обратными путями, где p может ссылаться на разные долгоживущие объекты во всех путях, возвращающих *p.

Ответ 2

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

Вы всегда можете добавить -Werror в gcc, чтобы сделать предупреждения с завершением компиляции с ошибкой!

Чтобы добавить еще одну любимую тему SO: хотите ли вы ++i++ вызвать ошибку компиляции?

Ответ 3

Если вы вернете указатель/ссылку на локальную внутреннюю функцию, поведение четко определено, если вы не разыщите указатель/ссылку, возвращаемую функцией.

Это поведение Undefined только тогда, когда кто-то пересматривает возвращенный указатель.

Является ли это Undefined Поведением или нет, зависит от кода, вызывающего функцию, а не от самой функции.

Таким образом, просто компилируя эту функцию, компилятор не может определить, является ли поведение Undefined или "Хорошо определено". Лучшее, что он может сделать, это предупредить вас о потенциальной проблеме, и это происходит!

Пример кода:

#include <iostream>

struct A
{ 
   int m_i;
   A():m_i(10)
   {

   } 
};  
A& foo() 
{     
    A a;
    a.m_i = 20;     
    return a; 
} 

int main()
{
   foo(); //This is not an Undefined Behavior, return value was never used.

   A ref = foo(); //Still not an Undefined Behavior, return value not yet used.

   std::cout<<ref.m_i; //Undefined Behavior, returned value is used.

   return 0;
}

Ссылка на стандарт С++:
раздел 3.8

До того, как срок жизни объекта запустился, но после того, как хранилище, которое будет занимать объект, было allo-cated 34) или, после окончания срока службы объекта и до того, как хранилище, которое объект занят, повторно используется или выпущен, любой указатель, который ссылается на место хранения, в котором находится или находится объект, может использоваться, но только ограниченным образом. Такой указатель относится к выделенному хранилищу (3.7.3.2) и использует указатель, как если бы указатель имел type void*, четко определен. Такой указатель может быть разыменован, но полученное значение l может использоваться только ограниченным образом, как описано ниже. Если объект будет или имеет тип класса с нетривиальным деструктором, а указатель используется как операнд выражения-удаления, программа имеет поведение Undefined. Если объект будет или имеет тип класса, отличного от POD, программа имеет поведение Undefined, если:

-.......

Ответ 4

Поскольку стандарт не ограничивает нас.

Если вы хотите стрелять на собственную ногу, вы можете это сделать!

Однако давайте посмотрим и пример, где это может быть полезно:

int &foo()
{
    int y;
}

bool stack_grows_forward()
{
    int &p=foo();
    int my_p;
    return &my_p < &p;
}

Ответ 5

Составители не должны отказываться от компиляции программ, если в стандарте не указано, что им разрешено это делать. В противном случае программам портов было бы намного сложнее, поскольку они не могли компилироваться с другим компилятором, даже если они соответствуют стандарту.

Рассмотрим следующую функцию:

int foobar() {
    int a=1,b=0;
    return a/b;
}

Любой достойный компилятор обнаружит, что я деляю на ноль, но он не должен отклонять код, так как я мог бы действительно инициировать сигнал SIG_FPE.

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

В стандарте Java на самом деле указаны некоторые правила проверки того, что непустые методы всегда возвращают значение. К сожалению, я недостаточно читал стандарт С++, чтобы узнать, что разрешить компилятору.

Ответ 6

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

Ответ 7

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

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