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

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

28.12.2023, 09:07. Показов 5540. Ответов 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
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
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
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
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
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
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
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6279 / 3003 / 1051
Регистрация: 01.06.2021
Сообщений: 11,236
30.12.2023, 22:47
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Однако исходная головоломка относится к современному С++ (С++17 и выше)
всё-таки об этом нужно было предупреждать еще в шапке темы вместе с условием, а не уже в ходе обсуждения
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
30.12.2023, 22:55  [ТС]
Цитата Сообщение от Royal_X Посмотреть сообщение
всё-таки об этом нужно было предупреждать еще в шапке темы вместе с условием, а не уже в ходе обсуждения
Нет, наоборот. Предупреждать нужно только тогда, когда речь идет о неактуальной версии спецификации языка. По умолчанию же всегда подразумевается самая актуальная версия. То есть вопрос обсуждается в контексте С++23 - это само собой разумеется.

Я еще могу понять необходимость такого уточнения сейчас при обсуждении чего-то специфичного для С++23. Но для С++17... !!!
0
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6279 / 3003 / 1051
Регистрация: 01.06.2021
Сообщений: 11,236
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
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
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
13206 / 6841 / 1822
Регистрация: 18.10.2014
Сообщений: 17,302
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
Ответ Создать тему
Новые блоги и статьи
Свет внутри себя
kumehtar 07.06.2026
Пусть это будет здесь lIs4oanZS9Y
Программа для com-порта
Uhbif79 05.06.2026
Всем привет, давно хотел изучить Qt, начинал, бросал, потом снова начинал. И сейчас вот смог написать свою первую программу. До этого имел опыт программирования микроконтроллеров, писал прошивки на. . .
Транскрипция 55-минутного видео через Whisper: WhisperDesktop облажался, спас Google Colab[
anaschu 01.06.2026
Понадобилось получить текст из свежезагруженного видео на YouTube. Казалось бы, задача на пять минут. Заняла полтора часа. Делюсь опытом — может кому пригодится последовательность решений. . . .
21 мат мед. Планы на развитие модели здравоСохранения
anaschu 01.06.2026
AnyLogic: план развития симуляционной модели рабочего коллектива — динамический абсентеизм, реальные данные, три сценария сравнения Продолжаю серию постов о дискретно-событийной модели рабочего. . .
20. Мат мед. Абсентеизм как отдельный тип простоя
anaschu 29.05.2026
Апдейт модели: исправленные баги, абсентеизм и новые механизмы Продолжаю развивать ранее описанную модель рабочего коллектива на AnyLogic. За последние несколько дней был проведён серьёзный. . .
19. здоровье, усталость и психотип работника влияют на производительность предприятия, и наоборот, производительность на здоровье, усталось и психотип
anaschu 28.05.2026
Дискретно-событийная модель рабочего коллектива на AnyLogic: здоровье, выгорание, психотипы и микростимуляция Привет, коллеги. Хочу поделиться итогами нескольких недель работы над симуляционной. . .
"Прокси" для последовательного порта
Eddy_Em 28.05.2026
Эту штуку написал я достаточно давно. Но сейчас вот понадобилось настроить датчик грозы, но при этом не отключать его от "метеодемона". Соответственно, надо запустить этот "прокси": метеодемон будет. . .
Рефакторинг программы уравнивания.
Massaraksh7 26.05.2026
Пример по предыдущей записи в блоге. Но, надо заметить, что, во-первых, там оптимизация не только математики, но и работы с базой данных, и с графами, а во-вторых, это ещё не всё.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru