Почему компиляторы выдают предупреждение о возврате ссылки на локальную переменную стека, если это неопределенное поведение? - PullRequest
25 голосов
/ 19 июля 2011

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

struct A{
};

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

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

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

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

Ответы [ 7 ]

22 голосов
/ 19 июля 2011

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

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.

10 голосов
/ 19 июля 2011

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

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

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

8 голосов
/ 19 июля 2011

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

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

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

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

Пример кода:

#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;
}

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

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

- .......

3 голосов
/ 19 июля 2011

Потому что стандарт не ограничивает нас.

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

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

int &foo()
{
    int y;
}

bool stack_grows_forward()
{
    int &p=foo();
    int my_p;
    return &my_p < &p;
}
2 голосов
/ 19 июля 2011

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

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

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

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

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

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

0 голосов
/ 19 июля 2011

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

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

0 голосов
/ 19 июля 2011

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

...