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

Почему возвращает ссылку на локальное значение функции, а не ошибку компиляции?

Следующий код вызывает поведение undefined.

int& foo()
{
  int bar = 1234;
  return bar;
}

g++ выдает предупреждение:

предупреждение: ссылка на локальную переменную bar возвращается [-Wreturn-local-addr]

clang++ тоже:

предупреждение: ссылка на стекную память, связанную с локальной переменной 'bar', возвращена [-Wreturn-stack-address]

Почему это не ошибка компиляции (игнорируя -Werror)?

Есть ли случай, когда верность ссылки на локальный var действительна?

РЕДАКТИРОВАТЬ Как указано, спецификация позволяет компилировать этот файл. Итак, почему спецификация не запрещает такой код?

4b9b3361

Ответ 1

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

Если вы укажете слишком мало, это не будет слишком полезно. И компиляторы, вероятно, уже проверили, чтобы это выдавало предупреждения, а реальные программисты компилируются с -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;
}

Что, конечно же, даст вам болтливую ссылку.

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

Ответ 2

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

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

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

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

Ответ 3

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

Эта функция:

 void* get_stack_pointer (void) { int x; return &x; };

AFAIK, это не поведение undefined, если вы не разыщите результирующий указатель.

гораздо более портативен, чем этот:

 void* get_stack_pointer (void) { 
    register void* sp asm ("%esp"); return sp; }

В связи с тем, почему вы можете получить указатель стека: ну, есть случаи, когда у вас есть веская причина для его получения: например, консервативный Сборщик мусора Boehm должен сканировать стек (так хочет указатель стека и нижняя часть стека).

И если вы вернули ссылку на С++, на которой вы могли бы использовать свой адрес только с помощью унарного оператора &, получение такого адреса было бы логичным (это ИМХО - единственная законная операция, которую вы можете сделать на нем).

Еще одна причина получить указатель стека - получить адрес указателя не NULL (который вы могли бы, например, хеш), отличаться от кучи, локальных или статических данных. Однако для этой цели вы можете использовать (void*)1 или (void*)-1.

Итак, компилятор прав, только предупреждая об этом.

Я предполагаю, что компилятор С++ должен принять

int& get_sp_ref(void) { int x; return x; }

void show_sp(void) { 
   std::cout << (&(get_sp_ref())) << std::endl; }

Ответ 4

Чтобы расширить более ранние ответы, стандарт ISO С++ не учитывает различия между предупреждениями и ошибками для начала; он просто использует термин "диагностика", когда ссылается на то, что компилятор должен испускать, увидев плохо сформированную программу. Цитирование N3337, 1.4, пункты 1 и 2:

Набор диагностируемых правил состоит из всех синтаксических и семантических правил в этом Международный стандарт, за исключением тех правил, которые содержат явные обозначения, что "нет диагностика" или которые описаны как приводящие к "undefined поведению."

Хотя в этом международном стандарте указаны только требования к реализациям на С++, эти требования часто легче понять, если они сформулированы как требования к программ, частей программ или выполнения программ. Такие требования имеют следующее значение:

  • Если программа не содержит нарушений правил в этом Международном стандарте, соответствующая реализация должна в пределах своих ресурсов принимать и правильно выполнять этой программы.

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

  • Если программа содержит нарушение правила, для которого не требуется диагностика, это В Международном стандарте нет требований к реализации в отношении этого Программа.

Ответ 5

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

Компилятор не обязан диагностировать, может ли функция быть вызвана или нет. Например, вы можете настроить программу, которая ищет контрпримеры для последней теоремы Ферма и вызывает эту функцию, если она ее найдет. Было бы ошибкой для компилятора отказаться от такой программы.

Ответ 6

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

Уже опубликованный текст Angew с локальной переменной, которая фактически является глобальной. Однако есть и другой (ИМХО) образец.

Object& GetSmth()
{
    Object* obj = new Object();
    return *obj;
}

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


ВАЖНОЕ ПРИМЕЧАНИЕ. Я не рекомендую и не рекомендую использовать такой стиль кодирования, потому что это плохо, обычно трудно понять, что происходит, и это приводит к проблемы, такие как утечки памяти или сбои. Это всего лишь образец, который показывает, почему эта конкретная ситуация не может рассматриваться как ошибка.