Рассмотрим следующую программу:
#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
возвращает в каждом из этих случаев?
@LanguageLawyer Не совсем так. Хотя соответствующие отрывки, кажется, пересекаются для обоих вопросов, я думаю, что сами вопросы разные.
Это, конечно, также применимо, если мы сделаем 42 значением макроса с предварительной обработкой. Я лично не понимаю, почему какой-либо стандарт требует дублирования идентичных неизменяемых.
Кажется, что все упустили из виду, что <<
здесь имеет определяемое реализацией поведение для указателей, поэтому, если вы определяете «другое» в поведении программы, оно просто не указано для произвольных соответствующих реализаций.
Возможно, вы можете перефразировать программу, используя вместо этого ==
. Обратите внимание, что могут быть другие проблемы в некоторых немного отличающихся случаях...
@FrankHB Я действительно рассматривал возможность использования ==
и других способов получения адреса в том же выражении, но, как вы указываете, у них были проблемы. <<
для указателей, определяемых реализацией, интересно. Я не думаю, что это особенно влияет на вопрос, поскольку ответ должен применяться для любого выбора реализации.
Здесь также есть еще один похожий вопрос: Заставить C++ назначать новые адреса аргументам
@OrçunÇolak Да, именно здесь возник этот вопрос, как видно из комментариев к принятому ответу.
Два разных объекта просто... не один и тот же, поскольку у них разные личности. Понятие идентичности, безусловно, используется более широко, т.е. для lvalue, хотя в большинстве контекстов от него также тщательно избегают. Если интересует разница в идентичности, спецификация просто определяет единственно разрешенные способы доступа к объектам (например, строгие правила псевдонимов), потому что количество объектов здесь считается деталью реализации. Адрес концептуально выводится из идентичности объектов, и он не может помочь вам сделать различие более очевидным.
Правильно полагаться на понятие адреса для описания расположения различных объектов (и их подобъектов). В этом конкретном контексте идентичности недостаточно. Здесь это не так (перекрывающееся время жизни, а не перекрывающееся хранилище). Рассуждение превращается в хаос, когда речь идет об адресах. Как было сказано, поскольку правила «как если бы» эффективны, разные объекты могут иметь один и тот же адрес, если нет переносимого способа различать адреса. Также обратите внимание, что addressof
и [[no_unique_address]]
на самом деле не требуют различать адреса (а только идентификаторы).
Два живых объекта в С++ (почти) всегда имеют разные адреса.
Поскольку временные файлы в #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].
Строковые литералы являются исключением среди всех типов литералов, потому что они классифицируются как lvalue и, следовательно, имеют адрес.
[lex.string.14] Вычисление строкового литерала приводит к объекту строкового литерала со статической продолжительностью хранения, инициализированному из заданных символов, как указано выше. Являются ли все строковые литералы разными (то есть хранятся в непересекающихся объектах) и дают ли последовательные вычисления строкового литерала один и тот же или другой объект, не указано.
Это означает, что литералы могут иметь тот же адрес, что и @RiaD, но это не противоречит сказанному выше, поскольку они являются одним и тем же объектом.
Все другие литералы, включая целые числа, являются выражениями prvalue, которые не являются объектами (в том смысле, что у них нет адреса), но в некоторых случаях они порождают временный объект посредством временной материализации, которая происходит для foo(42)
, потому что он привязан к const T&
. AFAIK стандарт прямо не говорит, что одни и те же два выражения prvalue должны порождать разные временные, но он говорит, что выражение инициализирует временное, поэтому я считаю, что каждое выражение должно создавать новое временное, время жизни также немного отличается. Итак, два адреса (если они соблюдены) должны быть разными.
Однако есть исключения (которые не относятся к рассматриваемому случаю). А именно, пустые базовые классы и члены с атрибутом [[no_unique_address]] могут совместно использовать адрес с другими подобъектами. И, конечно же, первый подобъект разделяет адрес своего суперобъекта.
но должны ли эти два 42-х быть разными объектами? Например, «abc» и «abc» могут быть одним и тем же массивом. gcc.godbolt.org/z/f4zPTP
или даже ближе к форме OP gcc.godbolt.org/z/rY793j (Обратите внимание, что это ссылка на фактический массив, переданный функции, на данный момент еще нет распада, поэтому это не будет объяснением)
@RiaD Отредактировал ответ, отличный вопрос!
Временные сохраняются до конца полного выражения, вызвавшего их к жизни.
[класс.временный]
4 ... Временные объекты уничтожаются на последнем этапе оценивая полное выражение ([intro.execution]), которое (лексически) содержит точку, где они были созданы.
Это верно для всех временных. Это означает, что в выражении № 3, при условии, что его вычисление завершается без создания исключения, оба временных объекта могут иметь перекрывающиеся времена жизни.
За некоторыми исключениями (ни одно из которых здесь не применимо), два разных объекта в течение своего жизненного цикла будут иметь разные адреса.
Некоторые из моих предыдущих комментариев повторно размещены здесь по запросу:
Самое интересное, что C++ не требует конкретной кодировки адреса объекта. (И в нем ничего не говорится об адресе функции, кстати.) Это естественно, потому что абстрактная машина C++ просто не интересуется адресом в большинстве контекстов.
Два разных объекта просто... не один и тот же, поскольку у них разные личности. Понятие идентичности, безусловно, используется более широко, т.е. для lvalue, хотя в большинстве контекстов от него также тщательно избегают. Если интересует разница в идентичности, спецификация просто определяет единственно разрешенные способы доступа к объектам (например, строгие правила псевдонимов), потому что количество объектов здесь считается деталью реализации. Адрес концептуально выводится из идентичности объектов, и он не может помочь вам сделать различие более очевидным.
Правильно полагаться на понятие адреса для описания расположения различных объектов (и их подобъектов). В этом конкретном контексте идентичности недостаточно. Здесь это не так (перекрывающееся время жизни, а не перекрывающееся хранилище). Рассуждение превращается в хаос, когда речь идет об адресах. Как было сказано, поскольку правила «как если бы» эффективны, разные объекты могут иметь один и тот же адрес, если нет переносимого способа различать адреса. Также обратите внимание, что addressof
и [[no_unique_address]]
на самом деле не требуют различать адреса (а только идентификаторы).
Отвечает ли это на ваш вопрос? Время жизни временных файлов C++ — безопасно ли это?