Стандарт C ++ утверждает, что возврат ссылки на локальную переменную (в стеке) является неопределенным поведением, так почему же многие (если не все) текущие компиляторы только выдают предупреждение для
struct A{
};
A& foo()
{
A a;
return a; //gcc and VS2008 both give this a warning, but not a compiler error
}
Разве не лучше, если бы компиляторы выдавали ошибку вместо предупреждения для этого кода?
Есть ли какие-то большие преимущества в том, чтобы позволить этому коду компилироваться только с предупреждением?
Обратите внимание, что это не о ссылке const
, которая могла бы продлить время жизни временного до времени жизни самой ссылки.
Практически невозможно проверить с точки зрения компилятора, возвращаете ли вы ссылку на временный объект. Если бы стандарт предписывал диагностировать ошибку, написание компилятора было бы практически невозможным. Рассмотрим:
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
.
Неопределенное поведение не является ошибкой компиляции, это просто не правильно сформированная программа C ++. Не каждая плохо сформированная программа несовместима, просто она предсказуема . Держу пари, что в принципе даже компьютер не может решить, является ли данный текст программы корректной программой C ++.
Вы всегда можете добавить -Werror
в gcc, чтобы предупреждения прекращали компиляцию с ошибкой!
Чтобы добавить еще одну любимую тему SO: Вы хотите, чтобы ++i++
также вызывал ошибку компиляции?
Если вы возвращаете указатель / ссылку на локальную внутреннюю функцию, поведение хорошо определено , если вы не разыменовываете указатель / ссылку, возвращенную из функции.
Это неопределенное поведение, только когда кто-то отменяет возвращаемый указатель.
Является ли это неопределенным поведением или нет, зависит от кода, вызывающего функцию, а не от самой функции.
Таким образом, просто во время компиляции функции компилятор не может определить, является ли поведение Undefined или Well Defined. Лучшее, что он может сделать, - это предупредить вас о потенциальной проблеме, и это так!
Пример кода:
#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*
, является четко определенным. Такой указатель может быть разыменован, но результирующее значение l может использоваться только ограниченным образом , как описано ниже. Если объект будет или имел тип класса с нетривиальным деструктором, и указатель используется в качестве операнда выражения удаления, программа имеет неопределенное поведение. Если объект будет или имел тип класса не POD, программа имеет неопределенное поведение, если:
- .......
Потому что стандарт не ограничивает нас.
Если вы хотите стрелять себе в ноги, вы можете сделать это!
Однако давайте посмотрим и пример, где это может быть полезно:
int &foo()
{
int y;
}
bool stack_grows_forward()
{
int &p=foo();
int my_p;
return &my_p < &p;
}
Это очень плохая практика - полагаться на это, но я верю, что во многих случаях (и это никогда не является хорошей ставкой), эта ссылка на память все равно будет действительной, если между временем не будет вызвано ни одной функции foo()
возвращается и время, когда вызывающая функция использует возвращаемое значение. В этом случае эта область стека не будет иметь возможности перезаписаться.
В C и C ++ вы можете выбрать доступ к произвольным разделам памяти в любом случае (конечно же, в пределах пространства памяти процесса) с помощью арифметики указателей, так почему бы не разрешить возможность создания ссылки на то место, где вы захотите?