Re[3]: Не могу найти пример бага, который я не понял
От: so5team https://stiffstream.com
Дата: 25.10.24 04:29
Оценка: 41 (5) +5 :)
Здравствуйте, Marty, Вы писали:

M>>https://rsdn.org/forum/flame.comp/8834755?tree=tree
Автор: so5team
Дата: 19.10 11:01


M>Ага, оно. А в чем там косяк?


Если вы сохраняете в объекте ссылку, то ваша задача убедиться в том, что ссылка не протухнет за время жизни вашего объекта. А если вы сохраняете константную ссылку, то наступить на эти грабли проще простого:
struct Data {
  const int & _i;
  explicit Data(const int & i) : _i{i} {}
};

Data d{42}; // Упс! Приплыли.


Проблема в том, что 42 -- это временный экземпляр типа int, фактически это rvalue (или как оно там по стандарту правильно называется).
Но ссылка на rvalue в С++ неявным образом преобразуется к const-ссылке, поэтому в Data уходит константная ссылка на временный объект.

Соответственно, как только завершается выражение, в котором конструктор вызывался, временный объект перестает существовать, а ссылка на него протухает. В Data::_i оказывается повисшая ссылка, это баг.

Начиная с C++11 от этого можно попробовать защититься запретив конструктор, который принимает rvalue references. Что и было показано в том примере.

Но это же C++, здесь на каждую хитрую задницу тут же отыскивается болт с нужной резьбой. В данном случае это вариант шаблона функции std::min от двух аргументов. Этот вариант принимает аргументы по константной ссылке и возвращает так же константную ссылку. При этом если в std::min передать временные объекты, то std::min примет их по константной ссылке и точно так же вернет константную ссылку на один из временных объектов. Со вполне предсказуемым протуханием этой ссылки после завершения выражения, в котором std::min был вызван. Что и позволяет отдать в Data ссылку, которая протухнет сразу же после завершения работы конструктора.

Т.е. пример демонстрирует как вполне себе легальная функция, присутствующая в стандартной библиотеке, позволяет обходить примитивную защиту от ссылок на временные объекты.

А дальше вступают в дело особенности C++, в которых простые int-ы -- это не вполне себе объекты, а хз, особенно в режиме оптимизации, а в самом языке есть UB из-за которых мы вообще не знаем как поведет себя программа при срабатывании UB (в частности, при обращении по протухшей ссылке). Из-за чего протухшая ссылка на временный int при запуске программы может вести себя "вроде бы нормально": в данном случае мы видим вполне ожидаемую печать значения 42.

Но если скомпилировать данный код с санитайзерами, то санитайзер (по крайней мере в GCC) на эту проблему укажет.

Забавно, что вместо int-а мог бы быть какой-то "настоящий" тип, вроде std::string. Но мы все равно могли бы получить вполне ожидаемое поведение в простейших случаях вроде вот такого:
Data d{std::min("A"s, "B"s)};
std::cout << "min is: " << d._i << std::endl;

Тут бы сыграло наличие в std::string т.н. SSO, из-за чего маленькие строки не хранятся в динамической памяти и в d могла бы попасть ссылка на кусок стека, который бы все еще содержал нужное нам значение.

PS. Очень двойственные чувства были по мере написания этого пояснения. С одной стороны, делится знаниями обязательно надо, т.к. сам много чему учишься именно на том, чем делятся другие. Но, с другой стороны, досадно, что дядлы вроде Shmj потянут подобные объяснения в очередную GPT чтобы та натренировалась еще больше и приблизило конец нашей профессии в привычном нам виде.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.