В каком точном операторе эта программа демонстрирует поведение Undefined в соответствии со стандартом C++?

(Мне известно, что следует избегать возврата адреса/ссылки на переменную, локальную для функции, и программа никогда не должна этого делать.)


Приводит ли возврат ссылки к локальной переменной/ссылке к неопределенному поведению? Или неопределенное поведение возникает только позже, когда используется возвращаемая ссылка (или «разыменовывается»)?

т. е. в каком именно операторе (#1 или #2 или #3) приведенный ниже пример кода вызывает неопределенное поведение? (Я написал свою теорию рядом с каждым)

#include <iostream>

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

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

int main()
{
   foo();                // #1 - Not UB; return value was never used
   A const &ref = foo(); // #2 - Not UB; return value still not yet used
   std::cout<<ref.m_i;   // #3 - UB: returned value is used
}

Мне интересно узнать, что в этом отношении указано в стандарте С++.

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

Приветствуются обсуждения того, как с этим справляются конкретные реализации, но, как я уже сказал, идеальным ответом будет ссылка на стандарт C++, который разъясняет это вне всяких сомнений.


person Alok Save    schedule 10.01.2012    source источник
comment
Строка № 2 использует возвращаемое значение, так как вызывается копирующий конструктор. Вы должны сделать main ref A&   -  person king_nak    schedule 10.01.2012
comment
ref является локальным объектом, поэтому № 3 не должен быть UB.   -  person iammilind    schedule 10.01.2012
comment
@king_nak: Спасибо. Исправил опечатку.   -  person Alok Save    schedule 10.01.2012
comment
@iammilind: Совершенно очевидно, что это UB. ref – это ссылка, относящаяся к объекту из другого блока, который больше не существует.   -  person Lightness Races in Orbit    schedule 10.01.2012
comment
@Als: Эту опечатку легко исправить: herbsutter.com/2008/01/01/   -  person Lightness Races in Orbit    schedule 10.01.2012
comment
@Als: лучше использовать A const& для № 2, иначе он не скомпилируется. Однако семантика того, о чем вы спрашиваете, не изменится.   -  person Lightness Races in Orbit    schedule 10.01.2012
comment
@LightnessRacesinOrbit: Да, спасибо, что указали на это. Только константная ссылка может быть привязана к временной. Я изменил ее. Думаю, это плохо, что я ее не скомпилировал. На самом деле я просто быстро взял ее из предыдущего ответа и изменил это соответствует Q.   -  person Alok Save    schedule 10.01.2012
comment
@LightnessRacesinOrbit: Упс, что там случилось :-S Спасибо за исправленную ссылку!   -  person Kerrek SB    schedule 10.01.2012
comment
@KerrekSB: Вы можете подтвердить то, что я сказал iammilind? Ни разу не проголосовали за мой комментарий, поэтому я волнуюсь, что сойду с ума.   -  person Lightness Races in Orbit    schedule 10.01.2012
comment
неправильная форма и УБ это разные вещи   -  person Lightness Races in Orbit    schedule 10.01.2012
comment
@Als: Некоторые люди завидуют. Что еще хуже, некоторые люди думают, что этот сайт предназначен только для наставничества/отладки вопросов, хотя на самом деле все наоборот!   -  person Lightness Races in Orbit    schedule 10.01.2012


Ответы (3)


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

[C++11: 8.3.2/5]: Не должно быть ссылок на ссылки, массивов ссылок и указателей на ссылки. Объявление ссылки должно содержать инициализатор (8.5.3), за исключением случаев, когда объявление содержит явный спецификатор extern (7.1.1), является объявлением члена класса (9.2) в определении класса или является объявлением параметра или тип возвращаемого значения (8.3.5); см. 3.1. Ссылка должна быть инициализирована для ссылки на действительный объект или функцию. [ Примечание: в частности, нулевая ссылка не может существовать в четко определенной программе, потому что единственный способ создать такую ​​ссылку означало бы привязать ее к «объекту», полученному путем разыменования нулевого указателя, что приводит к неопределенному поведению. Как описано в 9.6, ссылка не может быть привязана непосредственно к битовому полю. —конец примечания ]

Ссылка, возвращаемая функцией, представляет собой xvalue:

[C++11: 3.10/1]: [..] Значение x (значение «eXpiring») также относится к объекту, обычно близкому к концу его срока службы (например, его ресурсы могут быть перемещены). Значение x является результатом определенных видов выражений, включающих ссылки на значение r (8.3.2). [ Пример: Результатом вызова функции, тип возвращаемого значения которой является ссылка rvalue, является значение xvalue. —конец примера ] [..]

Это означает, что следующее не применяется:

[C++11: 12.2/1]: Временные объекты типа class создаются в различных контекстах: привязка ссылки к prvalue (8.5.3), возврат prvalue (6.6.3), преобразование, создающее prvalue (4.1, 5.2. 9, 5.2.11, 5.4), генерирование исключения (15.1), вход в обработчик (15.3) и некоторые инициализации (8.5).

[C++11: 6.6.3/2]: Оператор return без выражения и списка инициализации в фигурных скобках может использоваться только в функциях, которые не возвращают значение, т. е. функция с возвратом тип void, конструктор (12.1) или деструктор (12.4).

Оператор return с выражением непустого типа можно использовать только в функциях, возвращающих значение; значение выражения возвращается вызывающей стороне функции. Значение выражения неявно преобразуется в возвращаемый тип функции, в которой оно появляется. Оператор return может включать создание и копирование или перемещение временного объекта (12.2). [ Примечание. Операция копирования или перемещения, связанная с оператором return, может быть опущена или рассматриваться как rvalue для разрешения перегрузки при выборе конструктора (12.8). —конец примечания ] Оператор return с списком инициализации в фигурных скобках инициализирует объект или ссылку, которые должны быть возвращены из функции с помощью инициализации списка копирования (8.5.4). из указанного списка инициализаторов. [ Пример:

std::pair<std::string,int> f(const char* p, int x) {
   return {p,x};
}

—конец примера ]

Кроме того, даже если мы интерпретируем следующее как означающее, что выполняется инициализация нового ссылочного объекта, референт вероятно все еще жив в это время:

[C++11: 8.5.3/2]: Ссылка не может быть изменена для ссылки на другой объект после инициализации. Обратите внимание, что инициализация ссылки обрабатывается совершенно иначе, чем присваивание ей. Передача аргумента (5.2.2) и возврат значения функции (6.6.3) являются инициализациями.

  • Это делает #1 действительным.

Однако ваша инициализация новой ссылки ref внутри main явно нарушает [C++11: 8.3.2/5]. Я не могу найти формулировку для этого, но само собой разумеется, что область действия функции была закрыта при выполнении инициализации.

  • Это сделает №2 (и, следовательно, №3) недействительным.

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

person Lightness Races in Orbit    schedule 10.01.2012
comment
+1. За усилия и исследования. Это выглядит многообещающе. Мне потребуется некоторое время, чтобы мирно пропитать соответствующие разделы после работы, прежде чем я приму этот ответ. - person Alok Save; 11.01.2012
comment
Возвращаемая ссылка является lvalue, а не xvalue. В этом коде нет ссылки на rvalue. - person T.C.; 13.01.2015
comment
@Т.С. Мне кажется, вы путаете термины. Ссылка name rvalue называет тип; это не имеет ничего общего с категорией значения ссылки. И совершенно очевидно, что в A const& ref = foo() инициализатором является выражение rvalue (со ссылкой типа [lvalue] на A). Где бы вы сказали, что есть выражение lvalue? И где я утверждал, что был ссылочный тип rvalue (A&&)? - person Lightness Races in Orbit; 13.01.2015
comment
Ну, вы назвали это xvalue, а xvalue является результатом определенных видов выражений, включающих ссылки на rvalue (8.3.2). Выражение, имеющее тип lvalue reference to A, является выражением lvalue. Это даже указано в пуле прямо над пулей, которую вы процитировали. В качестве другого примера, результатом вызова функции, тип возвращаемого значения которой является ссылка lvalue, является lvalue. - person T.C.; 13.01.2015
comment
@Т.С. Вы правы на самом деле в том, что foo() является lvalue (упс в моем предыдущем комментарии), но в моем ответе (три года назад!) я говорю о месте, где инициализируется ссылка. Даже там вы правы, говоря, что в коде нет выражений xvalue. Прошло так много времени, что я даже не знаю, что здесь сейчас происходит. Вздох. - person Lightness Races in Orbit; 13.01.2015

Вот мой неполный и, возможно, недостаточный взгляд на этот вопрос:

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

Я думаю, что следующий гораздо более простой пример дает точно такую ​​​​же дилемму, как и ваш вопрос:

std::reference_wrapper<T> r;

{
    T t;
    r = std::ref(t);
}

// #1

В #1 ссылка внутри r уже недействительна, но программа в порядке. Просто не читай r.

В вашем примере строка № 1 в порядке, а строка № 2 — нет, потому что исходная строка № 2 вызывает A::A(A const &) с аргументом foo(), и, как уже говорилось, это не может инициализировать переменную аргумента функции с помощью действующая ссылка, как и ваша отредактированная версия A const & a = foo();.

person Kerrek SB    schedule 10.01.2012
comment
+1: я полностью согласен (хотя жаль, что в стандарте el нет ничего, что делало бы это явным, и я как бы хотел, чтобы просто имея недопустимую ссылку было UB). - person Lightness Races in Orbit; 10.01.2012
comment
@LightnessRacesinOrbit: Ну, подумайте об этом таким образом - если бы все это было написано с помощью указателей, не было бы даже путаницы. Я полагаю, все, что нам сейчас нужно, это фраза в стандарте, которая говорит, что использование ссылки на несуществующий объект является UB, или что-то в этом роде, параллельно с утверждением о разыменовывании указателей. - person Kerrek SB; 10.01.2012
comment
Но вы даже не можете скопировать недопустимые указатели, не так ли? Я имею в виду, строго говоря. - person Lightness Races in Orbit; 10.01.2012
comment
@LightnessRacesinOrbit: Хм, возможно, вы правы - единственными допустимыми значениями указателя являются те, которые являются адресами существующих объектов (или прошлых). Так что, возможно, ситуация с указателями точно такая же, как и со ссылками. - person Kerrek SB; 10.01.2012
comment
Спасибо за ответ. Это соответствует спекулятивному чувству, которое у меня есть по этому вопросу, но все же не удовлетворяет мое любопытство в отношении того, что Стандарт говорит об этом. UB для таких граничных условий с указателями/ссылками четко не указаны в Стандарте это непрекращающееся чувство, которое у меня есть. Не то чтобы это останавливало меня от написания какого-либо кода, но просто мне интересно, поскольку стандарт не определяет его (мы еще не смогли его найти), то как компиляторы интерпретируют эти правила. - person Alok Save; 10.01.2012
comment
@Als: я не думаю, что это так туманно: если значение указателя не является адресом живого объекта, вы не можете его использовать, вот так просто. Точно так же я бы сказал, что если адрес ссылки не является адресом живого объекта, у вас будет та же ситуация. - person Kerrek SB; 10.01.2012
comment
@KerrekSB: Как уже указал Томалак, и я согласен с тем, что вы все равно можете копировать недопустимые указатели, не вызывая UB. - person Alok Save; 10.01.2012
comment
@Als: мне было интересно узнать об этом последнем ... хотя практически, конечно, вы можете скопировать любой указатель на свое усмотрение, я не уверен, что об этом говорит стандарт - можете ли вы инициализировать объект к недопустимому значению? - person Kerrek SB; 10.01.2012
comment
@Als: К сожалению, очень сложно привести цитаты для этого. Семантика здесь представляет собой просто комбинацию всего, что говорит стандарт, вместе со всем, что он не говорит. Кроме того, для чего бы это ни стоило, это не кажется ужасно явным по этому вопросу в любом случае. - person Lightness Races in Orbit; 10.01.2012
comment
@LightnessRacesinOrbit: согласен. Стандарт говорит вам, что такое действительный указатель. Он не говорит, что все, что не объявлено действительным, недействительно, и это правильно. - person Kerrek SB; 10.01.2012
comment
Правила о преобразованиях lvalue/rvalue и куча дерьма в 3.8 приближаются к тому, что мы можем рационализировать. - person Lightness Races in Orbit; 10.01.2012
comment
Как насчет раздела 8.3.2/4 (из С++ 03)... Ссылка должна быть инициализирована для ссылки на действительный объект или функцию. ... Можно ли утверждать, что возвращенный A& будет связан с недопустимым объектом во время инициализации ref (что, предположительно, произойдет после возврата foo и уничтожения объекта, на который ссылается A&)? - person DRH; 10.01.2012
comment
Я согласен с вашими комментариями и комментариями @LightnessRacesinOrbit о том, что трудно привести прямую ссылку из Стандарта для этого и, следовательно, для вопроса. Я оставлю этот вопрос открытым и подожду, чтобы увидеть, появятся ли какие-то ответы. - person Alok Save; 10.01.2012
comment
@Als: я сделал то же самое для вопросов подобного формата. Удачи! - person Lightness Races in Orbit; 10.01.2012
comment
@DRH: О, это, вероятно, сработает... В конце концов, это это новая ссылка... (здесь две ссылки!) - person Lightness Races in Orbit; 10.01.2012

Я бы сказал №3. Сам по себе # 2 фактически ничего не делает, даже если объект, на который делается ссылка, уже находится вне области видимости. На самом деле это не проблема, связанная со стандартами, потому что это результат двух последовательных ошибок:

  1. Возврат ссылки на объект вне области видимости, за которым следует
  2. Использование ссылки.

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

person John McFarlane    schedule 10.01.2012
comment
В этом ответе нет авторитета. Это просто необоснованные предположения! - person Lightness Races in Orbit; 10.01.2012
comment
@Lightness: Я и не претендовал на это! - person John McFarlane; 10.01.2012
comment
@JMcF: Пожалуйста, без предположений, вопрос достаточно ясен, поэтому я просто ожидаю четкого ответа. Я бы не задавал вопрос, если бы я ожидал предположений, потому что даже я могу предположить, потому что для этого не нужны реальные доказательства. - person Alok Save; 10.01.2012
comment
@Als: Извиняюсь за недопонимание, но мне было непонятно. Вы сказали: чтобы было ясно, в каком именно утверждении (№ 1, № 2 или № 3) пример кода ниже демонстрирует неопределенное поведение, и я ответил на этот вопрос. Вы также сказали, что идеальный ответ будет ссылаться на ссылку, но вы не сказали, что разрешены только идеальные ответы. - person John McFarlane; 10.01.2012
comment
@JMcF: По этой логике я мог бы написать ответ, в котором говорится LOL BALLOONS! Воздушные шары были бы лучше здесь, которые были бы одинаково актуальны. В конце концов, он не спрашивал факты... - person Lightness Races in Orbit; 10.01.2012
comment
@Als: Вы могли бы, но это не так, и, как я цитировал, он сделал. В любом случае, я постараюсь уточнить в своем ответе. - person John McFarlane; 10.01.2012
comment
Я не вижу такой цитаты ни в одном из ваших сообщений. Однако сейчас это лучше. - person Lightness Races in Orbit; 10.01.2012