Гарантируется ли, что адреса двух временных объектов будут разными в одном и том же выражении?

Рассмотрим следующую программу:

#include <iostream>

int const * f(int const &i) 
{ 
  return &i; 
}

int main() 
{
  std::cout << f(42);  // #1
  std::cout << f(42);  // #2

  std::cout << f(42) << f(42);  // #3
}

В зависимости от компилятора и установленного уровня оптимизации адреса, напечатанные в строках #1 и #2, могут отличаться или не отличаться друг от друга.

Однако, независимо от выбора компилятора или уровней оптимизации, два адреса, напечатанные в строке #3, всегда отличаются друг от друга.

Вот демо, с которым можно поиграть.

Итак, каковы правила того, что f возвращает в каждом из этих случаев?

Отвечает ли это на ваш вопрос? Время жизни временных файлов C++ — безопасно ли это?

Language Lawyer 17.12.2020 14:23

@LanguageLawyer Не совсем так. Хотя соответствующие отрывки, кажется, пересекаются для обоих вопросов, я думаю, что сами вопросы разные.

cigien 17.12.2020 16:25

Это, конечно, также применимо, если мы сделаем 42 значением макроса с предварительной обработкой. Я лично не понимаю, почему какой-либо стандарт требует дублирования идентичных неизменяемых.

mckenzm 17.12.2020 20:24

Кажется, что все упустили из виду, что << здесь имеет определяемое реализацией поведение для указателей, поэтому, если вы определяете «другое» в поведении программы, оно просто не указано для произвольных соответствующих реализаций.

FrankHB 19.12.2020 21:16

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

FrankHB 19.12.2020 21:27

@FrankHB Я действительно рассматривал возможность использования == и других способов получения адреса в том же выражении, но, как вы указываете, у них были проблемы. << для указателей, определяемых реализацией, интересно. Я не думаю, что это особенно влияет на вопрос, поскольку ответ должен применяться для любого выбора реализации.

cigien 20.12.2020 08:40

Здесь также есть еще один похожий вопрос: Заставить C++ назначать новые адреса аргументам

Orçun Çolak 22.12.2020 06:50

@OrçunÇolak Да, именно здесь возник этот вопрос, как видно из комментариев к принятому ответу.

cigien 23.12.2020 04:27

Два разных объекта просто... не один и тот же, поскольку у них разные личности. Понятие идентичности, безусловно, используется более широко, т.е. для lvalue, хотя в большинстве контекстов от него также тщательно избегают. Если интересует разница в идентичности, спецификация просто определяет единственно разрешенные способы доступа к объектам (например, строгие правила псевдонимов), потому что количество объектов здесь считается деталью реализации. Адрес концептуально выводится из идентичности объектов, и он не может помочь вам сделать различие более очевидным.

FrankHB 31.12.2020 13:22

Правильно полагаться на понятие адреса для описания расположения различных объектов (и их подобъектов). В этом конкретном контексте идентичности недостаточно. Здесь это не так (перекрывающееся время жизни, а не перекрывающееся хранилище). Рассуждение превращается в хаос, когда речь идет об адресах. Как было сказано, поскольку правила «как если бы» эффективны, разные объекты могут иметь один и тот же адрес, если нет переносимого способа различать адреса. Также обратите внимание, что addressof и [[no_unique_address]] на самом деле не требуют различать адреса (а только идентификаторы).

FrankHB 31.12.2020 13:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
29
10
1 831
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Два живых объекта в С++ (почти) всегда имеют разные адреса.

Поскольку временные файлы в #1 #2 имеют неперекрывающиеся времена жизни, компилятор может повторно использовать хранилище #1 для #2 .

Но в #3 все временные объекты живы до конца выражения (по понятным причинам), и в этом случае они должны иметь разные адреса.

C++ не поддерживает гарантированное кэширование одних и тех же подвыражений, кроме правила "как если бы". Это означает, что если вы не берете адрес, для компилятора вполне разумно сохранить их, как он хочет, или не хранить их вообще.

Ссылка

Проект N4861 С++ 20 [6.7.9.2] Если объект не является битовым полем или подобъектом нулевого размера, адрес этого объекта - это адрес первого байта, который он занимает. Два объекта с перекрывающимися временами жизни, которые не являются битовыми полями, могут иметь один и тот же адрес, если один вложен в другой, или если в хотя бы один является подобъектом нулевого размера, и они имеют разные типов; в противном случае они имеют разные адреса и занимают непересекающиеся байт памяти. ^ 28

В вашем случае исключения не действуют. Сноска ^28 также говорит именно то, что я написал выше:

^28: В соответствии с правилом «как если бы» реализация может хранить два объекты по одному и тому же машинному адресу или вообще не сохранять объект, если программа не может заметить разницу.

Редактировать

Отличный вопрос от @RiaD:

Но должны ли эти два 42-х быть разными объектами? Например, «abc» и «abc» могут быть одним и тем же массивом.

Поведение зависит от типа используемого литерала и точно определено в N4861 Draft C++20 5.13 [lex.literal].

  1. Строковые литералы являются исключением среди всех типов литералов, потому что они классифицируются как lvalue и, следовательно, имеют адрес.

    [lex.string.14] Вычисление строкового литерала приводит к объекту строкового литерала со статической продолжительностью хранения, инициализированному из заданных символов, как указано выше. Являются ли все строковые литералы разными (то есть хранятся в непересекающихся объектах) и дают ли последовательные вычисления строкового литерала один и тот же или другой объект, не указано.

    Это означает, что литералы могут иметь тот же адрес, что и @RiaD, но это не противоречит сказанному выше, поскольку они являются одним и тем же объектом.

  2. Все другие литералы, включая целые числа, являются выражениями prvalue, которые не являются объектами (в том смысле, что у них нет адреса), но в некоторых случаях они порождают временный объект посредством временной материализации, которая происходит для foo(42), потому что он привязан к const T& . AFAIK стандарт прямо не говорит, что одни и те же два выражения prvalue должны порождать разные временные, но он говорит, что выражение инициализирует временное, поэтому я считаю, что каждое выражение должно создавать новое временное, время жизни также немного отличается. Итак, два адреса (если они соблюдены) должны быть разными.

Однако есть исключения (которые не относятся к рассматриваемому случаю). А именно, пустые базовые классы и члены с атрибутом [[no_unique_address]] могут совместно использовать адрес с другими подобъектами. И, конечно же, первый подобъект разделяет адрес своего суперобъекта.

eerorika 17.12.2020 11:26

но должны ли эти два 42-х быть разными объектами? Например, «abc» и «abc» могут быть одним и тем же массивом. gcc.godbolt.org/z/f4zPTP

RiaD 17.12.2020 23:36

или даже ближе к форме OP gcc.godbolt.org/z/rY793j (Обратите внимание, что это ссылка на фактический массив, переданный функции, на данный момент еще нет распада, поэтому это не будет объяснением)

RiaD 17.12.2020 23:40

@RiaD Отредактировал ответ, отличный вопрос!

Quimby 18.12.2020 10:24

Временные сохраняются до конца полного выражения, вызвавшего их к жизни.

[класс.временный]

4 ... Временные объекты уничтожаются на последнем этапе оценивая полное выражение ([intro.execution]), которое (лексически) содержит точку, где они были созданы.

Это верно для всех временных. Это означает, что в выражении № 3, при условии, что его вычисление завершается без создания исключения, оба временных объекта могут иметь перекрывающиеся времена жизни.

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

Некоторые из моих предыдущих комментариев повторно размещены здесь по запросу:

Самое интересное, что C++ не требует конкретной кодировки адреса объекта. (И в нем ничего не говорится об адресе функции, кстати.) Это естественно, потому что абстрактная машина C++ просто не интересуется адресом в большинстве контекстов.

Два разных объекта просто... не один и тот же, поскольку у них разные личности. Понятие идентичности, безусловно, используется более широко, т.е. для lvalue, хотя в большинстве контекстов от него также тщательно избегают. Если интересует разница в идентичности, спецификация просто определяет единственно разрешенные способы доступа к объектам (например, строгие правила псевдонимов), потому что количество объектов здесь считается деталью реализации. Адрес концептуально выводится из идентичности объектов, и он не может помочь вам сделать различие более очевидным.

Правильно полагаться на понятие адреса для описания расположения различных объектов (и их подобъектов). В этом конкретном контексте идентичности недостаточно. Здесь это не так (перекрывающееся время жизни, а не перекрывающееся хранилище). Рассуждение превращается в хаос, когда речь идет об адресах. Как было сказано, поскольку правила «как если бы» эффективны, разные объекты могут иметь один и тот же адрес, если нет переносимого способа различать адреса. Также обратите внимание, что addressof и [[no_unique_address]] на самом деле не требуют различать адреса (а только идентификаторы).

Другие вопросы по теме