Следующий код вызывает неопределенное поведение.
int& foo()
{
int bar = 1234;
return bar;
}
G ++ выдает предупреждение:
предупреждение: возвращена ссылка на локальную переменную ‘bar’ [-Wreturn-local-addr]
Clang ++ тоже:
предупреждение: возвращена ссылка на стековую память, связанную с локальной переменной ‘bar’ [-Wreturn-stack-address]
Почему это не ошибка компиляции (игнорирование -Werror
)?
Есть ли случай, когда возврат ссылки в локальную переменную действителен?
РЕДАКТИРОВАТЬ Как указано, спецификация обязывает это быть компилируемым. Итак, почему спецификация не запрещает такой код?
Я бы сказал, что требование сделать программу плохо сформированной (то есть сделать это ошибкой компиляции) значительно усложнит стандарт и принесет мало пользы. Вы должны были бы точно указать в стандарте, когда такие случаи должны быть диагностированы, и все компиляторы должны будут их реализовать.
Если вы укажете слишком мало, это не будет слишком полезно. И компиляторы, вероятно, уже проверяют это, чтобы выдавать предупреждения, а настоящие программисты компилируют с -Wall_you_can_give_me -Werror
тем не мение.
Если вы укажете слишком много, компиляторам будет сложно (или невозможно) реализовать стандарт.
Рассмотрим этот класс (для которого у вас есть только заголовок и библиотека):
class Foo
{
int x;
public:
int& getInteger();
};
И этот код:
int& bar()
{
Foo f;
return f.getInteger();
}
Теперь, должен ли быть написан стандарт, чтобы сделать это плохо сформированным или нет? Наверное, нет, что если Foo
реализован так:
#include "Foo.h"
int global;
int& Foo::getInteger()
{
return global;
}
В то же время это может быть реализовано так:
#include "Foo.h"
int& Foo::getInteger()
{
return x;
}
Что, конечно, дало бы вам свисающую ссылку.
Моя точка зрения заключается в том, что компилятор не может действительно знать, нормально ли возвращает ссылку, за исключением нескольких тривиальных случаев (возвращая ссылку на автоматическую переменную области действия функции или параметр не ссылочного типа). Я не думаю, что стоит усложнять стандарт для этого. Тем более что большинство компиляторов уже предупреждают об этом как о качестве реализации.
Кроме того, потому что вы можете захотеть получить текущий указатель стека (что бы это ни значило в вашей конкретной реализации).
Эта функция:
void* get_stack_pointer (void) { int x; return &x; };
AFAIK, это не неопределенное поведение, если вы не разыменовываете результирующий указатель.
гораздо более портативен, чем этот:
void* get_stack_pointer (void) {
register void* sp asm ("%esp"); return sp; }
Относительно того, почему вы можете захотеть получить указатель стека: ну, есть случаи, когда у вас есть веская причина получить его: например, консервативный Бём сборщик мусора нужно сканировать стек (поэтому хочет указатель стека и дно стека).
И если вы вернули ссылку на C ++, для которой вы бы взяли только ее адрес, используя &
Унарный оператор, получение такого адреса является законным IIUC (это ИМХО только законная операция, которую вы можете сделать на нем).
Другая причина получить указатель стека состоит в том, чтобы получитьNULL
Адрес указателя (который вы можете, например, хэш) отличается от любой кучи, локальных или статических данных. Тем не менее, вы могли бы использовать (void*)1
или же (void*)-1
для этой цели.
Так что компилятор прав, только предупреждая об этом.
Я думаю, что компилятор C ++ должен принять
int& get_sp_ref(void) { int x; return x; }
void show_sp(void) {
std::cout << (&(get_sp_ref())) << std::endl; }
По той же причине C позволяет вам возвращать указатель на освобожденный блок памяти.
Это действует в соответствии со спецификацией языка. Это ужасно плохой идея (и нигде близко чтобы гарантировать работу), но это все еще действует, поскольку это не запрещено.
Если вы спрашиваете Зачем стандарт допускает это, вероятно потому, что когда ссылки были введены, они работали именно так. У каждой итерации стандарта есть определенные руководящие указания (например, сведение к минимуму возможности «прерывания изменений», которые делают существующие правильно сформированные программы недействительными), и стандарт представляет собой соглашение между пользователем и исполнителем, с несомненно большим количеством разработчиков, чем сидящих пользователей в комитетах 🙂
Возможно, стоит продвинуть эту идею как потенциальное изменение и посмотреть, что скажет ISO, но я подозреваю, что это будет считаться одним из «переломных изменений» и, следовательно, очень подозрительным.
Чтобы расширить предыдущие ответы, стандарт ISO C ++ не отражает различий между предупреждениями и ошибками; он просто использует термин «диагностика», когда ссылается на то, что должен выдать компилятор при просмотре плохо сформированной программы. квотирование N3337, 1.4, пункты 1 и 2:
Набор диагностируемых правил состоит из всех синтаксических и семантических правил в этом
Международный стандарт, за исключением тех правил, которые содержат четкое обозначение, что «нет
требуется диагностика »или которые описываются как приводящие к« неопределенному поведению ».Хотя этот международный стандарт устанавливает только требования к реализациям C ++,
эти требования часто легче понять, если они сформулированы как требования к
программы, части программ или выполнение программ. Такие требования имеют
следующее значение:
Если программа не содержит нарушений правил настоящего международного стандарта,
соответствующая реализация должна в пределах своих ресурсов принимать и правильно выполнять
эта программа.Если программа содержит нарушение какого-либо диагностируемого правила или возникновение
конструкция, описанная в настоящем стандарте как «условно поддерживаемая», когда
реализация не поддерживает эту конструкцию, соответствующая реализация должна выдавать
хотя бы одно диагностическое сообщение.Если программа содержит нарушение правила, для которого не требуется диагностика, это
Международный стандарт не предъявляет требований к реализации в отношении этого
программа.
Что-то еще не упоминается в других ответах, так это то, что этот код в порядке, если функция никогда не вызывается.
Компилятору не требуется диагностировать, может ли функция когда-либо вызываться или нет. Например, вы можете настроить программу, которая ищет контрпримеры к последней теореме Ферма и вызывает эту функцию, если она ее найдет. Для компилятора было бы ошибкой отклонить такую программу.
Возвращать ссылку в локальную переменную — плохая идея, однако некоторые люди могут создавать код, который требует этого, поэтому компилятор должен только предупреждать об этом и не определять допустимый код (допустимая структура) как ошибочный.
Angew уже размещен образец с местный переменная, которая на самом деле является глобальной. Однако есть и другой (ИМХО лучше) образец.
Object& GetSmth()
{
Object* obj = new Object();
return *obj;
}
В этом случае ссылка на локальный объект действительна, и вызывающая сторона после использования должна освободить память.
ВАЖНАЯ ЗАМЕТКА Я не поощряю и не рекомендую использовать такой стиль кодирования, потому что это плохой, обычно трудно понять, что происходит, и это приводит к некоторым проблемам, таким как утечки памяти или сбои. Это просто пример, который показывает, почему эту конкретную ситуацию нельзя трактовать как ошибку.