Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.76/25: Рейтинг темы: голосов - 25, средняя оценка - 4.76
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333

Головоломка для тех, кому под Новый Год нечего делать

28.12.2023, 09:07. Показов 5608. Ответов 34
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Тема на самом деле уже не раз упоминалась, но тем не менее... Пример кода

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
struct S
{
  void*& r;
  S(void*& r) : r(r) {}
  ~S() { r = nullptr; }
};
 
void* foo(S s)
{
  return s.r;
}
 
int main()
{
  void* p = &p;
  p = foo(p);
  std::cout << p << std::endl;
}
После компиляции под Clang/GCC этот код печатает нулевой указатель. После компиляции под MSVC++ код печатает ненулевой указатель (т.е. исходное значение p).

Головоломка: кто прав согласно спецификации языка?
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
28.12.2023, 09:07
Ответы с готовыми решениями:

Кому скучно и нечего делать, у меня есть нерабочий код
проблема нетривиальная. Вообщем я вроде всё в коде подписал. привожу пример того как должен работать класс на словах: Мы взяли число 73,...

Если кому-то нечего делать по выходным в ближайшие полтора года
Просто делюсь интересной ссылкой) http://www.cppgm.org/

Крестики-нолики. Кому делать нечего научите как исправить ошибку
Возникает при результате ничья. File &quot;6tick-tack-toe.py&quot;, line 212, in &lt;module&gt; main() File &quot;6tick-tack-toe.py&quot;, line...

34
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
29.12.2023, 22:24  [ТС]
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Undisputed Посмотреть сообщение
Если принять это за ответ, то получается, что в общем мы не имеем права возвращать значение полей которые относятся к объекту-аргументу и полагать, что получим то, что хотели. Особенно грабли будут, если этот объект из какой нибудь библиотеки.
Не понимаю этого замечания. "Не имеем права возвращать" по сравнению с чем? Приведите пример: где мы "имели право возвращать", а теперь якобы "не имеем права из возвращать", если принять мой ответ?

При чем здесь значения полей? Значения полей живут своей жизнью, никак не связанной с самими полями. Почему это мы "не имеем права" их возвращать?

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

В упор не понимаю.

Цитата Сообщение от Undisputed Посмотреть сообщение
наиболее правильный с моей точки зрения вариант развития событий я изложил в #14
#14 основан на устаревших представлениях о существовании какого-то временного объекта, которого больше не существует ни в коем случае. Ничего, относящегося к современному С++, я в #14 не вижу.

А вот это утверждение из #14

Цитата Сообщение от Undisputed Посмотреть сообщение
он этот объект не может быть уничтожен прежде чем значение его поля s.r будет записано в переменную p
грубейше ошибочно. Язык С++ нигде и никак не требует, чтобы в переменную p проводилось некое прямое копирование данных именно и непосредственно из поля s.r. Функция foo возвращает prvalue. Никакой привязки именно к полю s.r у возвращаемого prvalue нет. Нас интересует только значение этого поля. Но язык не обязан брать его из этого поля в некий "последний момент, непосредственно перед присваиванием в p".
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
29.12.2023, 23:21
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Как из этого можно было сделать вывод, что продление времени жизни приводит к "не имеем права возвращать"???
Очень просто. Под "не имеем право" подразумевается, что не имеем право полагаться на то, что на выходе значение p будет соответствовать тому значению, которое было при return. Де-факто значение которое будет в p после вызова функции foo (в выражении p = foo(p); ) зависит от того, будет продление lifetime объекта s или нет. Следовательно, начиная от С++17, в рамках языка, когда мы в этом коде пишем p = foo(p);, то язык нам не говорит, каким будет значение p. А если мы не знаем результата, то в рамках языка такая запись не имеет смысла, но она может иметь смысл в рамках конкретного компилятора и конкретной его версии.
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
29.12.2023, 23:50  [ТС]
Цитата Сообщение от Undisputed Посмотреть сообщение
Де-факто значение которое будет в p после вызова функции foo (в выражении p = foo(p); ) зависит от того, будет продление lifetime объекта s или нет.
Нет, нет, нет, ни в коем случае. Возвращаемое значение функции foo является prvalue. Ни от каких продлений и ни от каких lifetime оно ни в коем случае не зависит.

Зависимость от lifetime появилась бы, если бы foo возвращала void *&. Но она возвращает void *. Это всегда абсолютно безопасно.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
30.12.2023, 07:15
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Нет, нет, нет, ни в коем случае. Возвращаемое значение функции foo является prvalue. Ни от каких продлений и ни от каких lifetime оно ни в коем случае не зависит.
Думаю в данном случае вы чересчур заостряете внимание на формальностях... Конечно, когда p на выходе получает значение nullptr, то это не из-за возвращаемого значения, а из-за изменения его значения по ссылке. Поэтому я и использовал слово "Де-факто".

Когда мы пишем p = foo(p);, хотелось бы иметь возможность полагаться на конкретное значение p подобно тому, как мы уверены что int result = sum(2, 2); будет равно 4, а не 200 или 4 в зависимости от компилятора.
0
38 / 27 / 13
Регистрация: 18.12.2023
Сообщений: 74
30.12.2023, 09:40
Решил почитать ISO/IEC 14882 2020 Года, если я правильно понял, то поведение GCC и Clang соответствует стандарту C++, где указатель p должен быть сброшен в nullptr после завершения вызова foo, но до присваивания результата функции переменной p.
Ссылаюсь на пункты
class.temporary про описание жизненых циклов временных переменных
basic.life - общее описание жизненного цикла объектов, от начала жизни до вызова его деструктора
class.copy.elision - о создании или уничтожении объектов при передаче значений функции и возврате из них

N4860.pdf
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
30.12.2023, 22:45  [ТС]
Цитата Сообщение от Van_Darkholme Посмотреть сообщение
Решил почитать ISO/IEC 14882 2020 Года, если я правильно понял, то поведение GCC и Clang соответствует стандарту C++, где указатель p должен быть сброшен в nullptr после завершения вызова foo, но до присваивания результата функции переменной p.
Нет, все наоборот. Поведение именно MSVC++ соответствует "классическому" стандарту С++, то есть C++98, в котором действительно "указатель p должен быть сброшен в nullptr после завершения вызова foo, но до присваивания результата функции переменной p"

Поведение GCC/Clang нарушает этот стандарт. Однако это поведение (являющееся во многих отношениях более логичным и практичным) было, наконец, канонизировано в С++17.

Именно по этой причине в данном вопросе все компиляторы правы (начиная с С++17). Обратите внимание, что стандарт четко квантифицирует два варианта допустимого поведения: либо параметры удаляются сразу повел завершения вызова функции, либо живут до завершения полного выражения. Удалять параметры "где по посередине" между этими моментами не разрешается.

Цитата Сообщение от Van_Darkholme Посмотреть сообщение
Ссылаюсь на пункты
class.temporary про описание жизненых циклов временных переменных
basic.life - общее описание жизненного цикла объектов, от начала жизни до вызова его деструктора
class.copy.elision - о создании или уничтожении объектов при передаче значений функции и возврате из них
Нет. Относящийся к теме пункт стандарта (с соответствующим изменением в С++17) были уже упомянуты в сообщениях #15 и #16.

---

Интересно, кстати, заметить, что это означает, что в компиляторах, придерживающихся Itanium ABI, то есть откладывающих момент деструкции параметров, из функции можно (!) возвращать ссылки/указатели на их параметры, помня при этом, что эти ссылки/указатели далее в вызывающем коде можно использовать не позднее конца полного выражения. Разумеется, ничего хорошего в такой практике нет по целому ряду причин, но компиляторы, выбравшие такой implementation-defined вариант, обязаны гарантировать работоспособность такого кода.

При этом GCC, какое-то время назад начавший крестовый поход против возвращения ссылок/указателей на локальные переменные (и взамен подставляющий нулевые ссылки/указатели!), умудряется выдать предупреждение на такое возвращение и также возвращает взамен нулевую ссылку/указатель. То есть компилятор GCC до легализации поздней деструкции параметров нарушал стандарт языка, а сейчас, добившись легализации поздней деструкции, по-прежнему реализует ее некорректно , т.е. опять нарушает стандарт языка, но по-другому ¯\_(ツ)_/¯

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

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string>
#include <iostream>
 
std::string &foo(std::string s)
{
  s += "!!!\n";
  return s;
}
 
int main() 
{
  // Возьмем строки подлиннее, во избежание SSO
  std::cout << 
    foo("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua") <<
    foo("Consequat nisl vel pretium lectus quam id leo in. Lorem dolor sed viverra ipsum nunc aliquet bibendum enim facilisis") <<
    foo("Nec nam aliquam sem et. Platea dictumst quisque sagittis purus sit amet volutpat.") <<
    foo("Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam.") <<
    std::endl;
}
В GCC он сопровождается бессмысленным предупреждением о возвращении ссылки на "локальную" переменную и при запуске падает из-за того, что GCC "назло пользователю" возвращает нулевую ссылку из функции. В Clang код работает прекрасно, хотя тоже сопровождается аналогичным бессмысленным предупреждением. То есть эти компиляторы еще толком не сумели корректно реализовать то, чего сами добивались. Выдачу предупреждения еще можно как-то худо-бедно оправдать в качестве portability warning, но возвращение нулевой ссылки в GCC оправдать никак нельзя.

Подавить и предупреждение, и возвращение нулевой ссылки, однако, несложно

C++
1
2
3
4
5
6
std::string &foo(std::string s)
{
  s += "!!!\n";
  std::string *r = &s;
  return *r;
}
но удивляет тот факт, что этим приходится заниматься в компиляторах, которые "за это сражались".
0
Нарушитель
Эксперт функциональных языков программированияЭксперт С++
6318 / 3043 / 1054
Регистрация: 01.06.2021
Сообщений: 11,616
30.12.2023, 22:47
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Однако исходная головоломка относится к современному С++ (С++17 и выше)
всё-таки об этом нужно было предупреждать еще в шапке темы вместе с условием, а не уже в ходе обсуждения
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
30.12.2023, 22:55  [ТС]
Цитата Сообщение от Royal_X Посмотреть сообщение
всё-таки об этом нужно было предупреждать еще в шапке темы вместе с условием, а не уже в ходе обсуждения
Нет, наоборот. Предупреждать нужно только тогда, когда речь идет о неактуальной версии спецификации языка. По умолчанию же всегда подразумевается самая актуальная версия. То есть вопрос обсуждается в контексте С++23 - это само собой разумеется.

Я еще могу понять необходимость такого уточнения сейчас при обсуждении чего-то специфичного для С++23. Но для С++17... !!!
0
Нарушитель
Эксперт функциональных языков программированияЭксперт С++
6318 / 3043 / 1054
Регистрация: 01.06.2021
Сообщений: 11,616
30.12.2023, 22:59
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
То есть вопрос обсуждается в контексте С++23
поэтому в коде iostream, cout вместо print? раз обсуждается С++23, тогда код пускай тоже будет современным )
0
30.12.2023, 23:01  [ТС]

Не по теме:

Цитата Сообщение от Royal_X Посмотреть сообщение
поэтому в коде iostream, cout вместо print? раз обсуждается С++23, тогда код пускай тоже будет современным )
Не надо пытаться замусорить тему.

0
Комп_Оратор)
Эксперт по математике/физике
 Аватар для IGPIGP
9007 / 4708 / 630
Регистрация: 04.12.2011
Сообщений: 14,003
Записей в блоге: 16
04.01.2024, 12:20
Я пишу в этой теме без особой радости ввиду того, что моё отношение к ТС достаточно негативно. Не вдаваясь в подробности, я должен сказать, что причиной является выступление ТС в негативном комментарии к моему посту Объясните пожалуйста что делает конструкция *& в нашей функции и предоставленная ссылка сюда. Теперь о текущей теме. Моё мнение близко ко мнению Undisputed. Аргументы следующие. Вот функция:
C++
1
2
3
4
void* foo(S s)
{
  return s.r;
}
Давайте посмотрим как размещается служебный код, связанный с разрушением локальных объектов. Он генерируется и выполняется так как если бы в псевдокоде он располагался после оператора return:
C++
1
2
3
4
5
void* foo(S s)
{
  return s.r;
 destruct_everything();//тут запускается деструктор s
}
Это, с моей точки зрения, означает, что сначала в возвращаемый по значению указатель void* должно быть скопировано значение по ссылке на объект ещё живой на момент выполнения оператора return:
C++
1
2
3
4
5
void* foo(S s)
{
  return s.r;//вот тут у s.r значение до срабатывания деструктора
 destruct_everything();//тут запускается деструктор s который никак не должен повлиять на возвращаемую копию
}
Поэтому я считаю, что компилятор microsoft Visual C++ Cl работает корректно. Ценность темы в том, что она показывает некорректность работы (моё мнение, опять же) компиляторов G++/Clang в данном случае. Они изменяют логическую последовательность действий, приводящую к странному результату, отрабатывая так будто функция возвращает ссылку на указатель. Но если бы написали:
C++
1
2
3
4
void*& foo(S s)//ссылку возвращаем
{
  return s.r; 
}
то все 3 компилятора дали бы одинаковый результат, конечно.
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
04.01.2024, 21:13  [ТС]
Цитата Сообщение от IGPIGP Посмотреть сообщение
Я пишу в этой теме без особой радости[...]Моё мнение близко ко мнению[...]
Давайте посмотрим как размещается служебный код, связанный с разрушением локальных объектов.
C++
1
2
3
4
5
void* foo(S s)
{
  return s.r;
 destruct_everything();//тут запускается деструктор s
}
[...]она показывает некорректность работы (моё мнение, опять же) компиляторов[...]
"тут запускается деструктор s" ¯\_(ツ)_/¯ Дальше можно не читать...

В качестве резюме лишь компактно реитерирую уже сказанное выше: целью этой темы являются иллюстрация того малопонятного новичкам факта, что параметры функции НЕ являются локальными объектами этой функции. Параметры функции принадлежат вызывающему коду. А также иллюстрация того факта, что если ранее эти свойства параметров существенной роли не играли (кроме, разве что, вопросов обработки исключений), то начиная с С++17 они начинают иметь существенно более заметные и далеко идущие последствия. Приведенный искусственный пример как раз и иллюстрирует один из вариантов таких последствий.

Пока у вас не возникнет понимания этого вопроса, то есть пока вы будете упёрто расценивать параметры функций как ее "локальные объекты", вам рано или поздно придется столкнуться с неожиданными ситуациями, не вписывающимися в круг ваших заблуждений. И тут вам придется либо заставить себя разобраться в теме, либо... "без особой радости" продолжать выдумывать "свои мнения" и притягивать за уши теории о "некорректности работы компиляторов" в отчаянных попытках натянуть сову своих домыслов на глобус реальности.
0
Комп_Оратор)
Эксперт по математике/физике
 Аватар для IGPIGP
9007 / 4708 / 630
Регистрация: 04.12.2011
Сообщений: 14,003
Записей в блоге: 16
04.01.2024, 22:17
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
что параметры функции НЕ являются локальными объектами этой функции
Где я написал подобное? Нигде. Параметры функции это часть её объявления. Аргументы или фактические параметры, это объекты передаваемые в рантайме. Они принадлежат вызывающему коду. Объекты внутри оператора функции объявленные через формальные параметры, инициализируются при передаче, передаваемыми объектами. "Передача фактических параметров в функцию имеет семантику инициализации", я не готов процитировать дословно, но именно это по смыслу, утверждает Б. Страуструп. И это мало относится к тому, что побочный эффект от работы деструктора вызываемого при выходе из области оператора функции автоматически, влияет на возвращаемое значение. Если стандарт в 23 версии, это узаконил, с этим придётся жить. Но весело будет тем, у кого код нормально работал по классике и поломался. Рискну предположить, что для многопоточности тоже будут вопросы. Сегодня, ещё можно (или вчера) было защитить строго выделенную и ограниченную секцию кода. Но если на возвращаемое значение, влияет уже служебная часть, выполняемая после оператора возврата, то всё уже непонятно.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
05.01.2024, 14:48
IGPIGP,
На самом деле возврат поля это лишь частный случай проблемы. Приведу пример утечки памяти, которую на уровне языка не получится отследить. Представим что функция foo выглядит вот так
C++
1
2
3
4
void* foo(S s)
{ 
    return new int;
}
Дело в том, что если мы напишем p = foo(p);, то язык нам не говорит, будет ли в итоге утечка памяти или нет. Если разрушение объекта s будет перед присваиванием, то мы получим адрес и сможем освободить соответствующий участок памяти. Если же объект s будет разрушен после присваивания, то мы получим утечку без возможности освободить выделенную память. Более того, если функция что-то считает и записывает результат в динамически выделенную память, то все эти расчеты благополочно вылетят в трубу.

Добавлено через 3 минуты
Основная претензия здесь в том, что это implementation defined, что на мой взгляд некорректно. Думаю, здесь должна быть конкретика, а желающие какую нибудь экзотику компилятописатели, могут сделать фичу в своем компиляторе и включать ее через флаг
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13214 / 6847 / 1825
Регистрация: 18.10.2014
Сообщений: 17,333
05.01.2024, 19:23  [ТС]
Цитата Сообщение от Undisputed Посмотреть сообщение
Основная претензия здесь в том, что это implementation defined, что на мой взгляд некорректно. Думаю, здесь должна быть конкретика
Implementation defined - это и есть конкретика. Документированная конкретика, но только на уровне компилятора. Речь все таки идет об implementation defined, а не об unspecified.

Цитата Сообщение от Undisputed Посмотреть сообщение
а желающие какую нибудь экзотику компилятописатели, могут сделать фичу в своем компиляторе
Для этого авторы компиляторов должны сначала договориться, что является экзотикой, а что нет. В данном случае можно привести вполне логичные аргументы в пользу обоих вариантов поведения.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
05.01.2024, 19:23

Ищу консультанта/учителя С под Linux, Для тех,кому интересно делиться знаниями
Здравствуйте! Начал изучать Си под Linux при решении реальных задач. Ищу человека, для изучения на постоянной основе. Если вы...

Написал программу от делать нечего, она оказалась полезной. А дальше что с ней делать?
Просто для себя накидал на C# за пару дней довольно узкоспециализированную и простенькую программу. Все кто видел ее, отмечают ее удобство...

Для тех, кому скучно
Тут есть простенькое задание на языке лисп уровня первого курса прикладной информатики. Кто хочет, может по приколу сделать и похвастаться...

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

Тест-игра (для тех, кому за 18)
Привет всем! Попался как то мне старый номер журнала PLAYBOY (купил как то в детстве ) и нашел там очень интересный тест-игру... (детям...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
35
Ответ Создать тему
Новые блоги и статьи
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
[EasyBuilder Pro] Памятка по разработке для панелей Weintek
ФедосеевПавел 26.08.2026
Памятка по разработке для панелей Weintek ВВЕДЕНИЕ Ранее, при реализации проектов основное внимание уделял разработке управляющей программы для контроллера, а панели оператора доставалось время. . .
Модель по догадкам
anaschu 25.08.2026
Прошло две недели. Я уже рассказывал, как разговаривал с сотрудниками у сортировки и как понял, что главная ветка — не про приёмку, а про отбор. Но тогда я думал, что понял механику. На этой неделе я. . .
Запись в регистр сведений независимо от заполненности табличной части
Maks 25.08.2026
Реализация из решения ниже выполнена на нетиповом документе с несколькими табличными частями, разработанного в КА2. Задача: Обеспечить запись документа в регистр сведений независимо от. . .
Ноутбук Альфария
kumehtar 24.08.2026
Встретился тут в сети ноутбук Альфария, примарха Альфа-Легиона. Хотя возможно, это ноутбук Омегона, разумеется. Ну как вам?
Мастера простых решений
DevAlt 23.08.2026
В сишарп стэках winforms, да и wpf существует сложная система связывания источниках данных и элементов формы(текстовые поля и метки), опирается все это на технологию событий и мета. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru