Семантика перемещения C++ и вызов функции

Я обновляю свои навыки C++ и заново изучаю семантику движений. Вот пример программы и вывод, который я не могу понять.

#include <iostream>
#include <iomanip>

int sepI = 0;
void printSep(const char* msg) {
    std::cout
        << std::setw(40) << std::setfill('>') << "" << std::setfill(' ')
        << std::setw(2) << std::setiosflags(std::ios::right) << sepI++ << ": " << std::resetiosflags(std::ios::right)
        << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::endl;
}

void printHook(const char* msg, void* p) {
    std::cout
        << std::setw(25) << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::setw(15) << std::setiosflags(std::ios::right) << p << std::resetiosflags(std::ios::right)
        << std::endl;
}

class MyT {
public:
    MyT() { printHook("constructor call", this); }
    MyT(MyT& t) { printHook("copy constructor call", this); }
    MyT(MyT&& t) { printHook("move constructor call", this); }

    ~MyT() { printHook("destructor call", this); v = 0; }

    MyT& operator=(MyT& t) { printHook("copy assigment call", this); return *this; }
    MyT& operator=(MyT&& t) { printHook("move assigment call", this); return *this; }

    int v = 1;
};

MyT fn() { return MyT(); }

int main() {
    printSep("a");
    MyT a;

    printSep("MyT&& v1 = std::move(a)");

    MyT&& v1 = std::move(a);
    std::cout << v1.v << " " << &v1 << std::endl;

    printSep("MyT&& v2 = MyT()");

    MyT&& v2 = MyT();
    std::cout << v2.v << " " << &v2 << std::endl;

    printSep("MyT&& v3 = std::move(MyT())");

    MyT&& v3 = std::move(MyT());
    std::cout << v3.v << " " << &v3 << std::endl;

    printSep("MyT&& v4 = fn()");

    MyT&& v4 = fn();
    std::cout << v4.v << " " << &v4 << std::endl;

    printSep("MyT&& v5 = std::move(fn())");

    MyT&& v5 = std::move(fn());
    std::cout << v5.v << " " << &v5 << std::endl;

    printSep("end");

    return 0;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 0: a                            OK.
constructor call          0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1: MyT&& v1 = std::move(a)      OK. Makes sense.
1 0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2: MyT&& v2 = MyT()             OK. Makes sense.
constructor call          0x7ffe656eeae8
1 0x7ffe656eeae8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 3: MyT&& v3 = std::move(MyT())  NOT OK.
constructor call          0x7ffe656eeaec
destructor call           0x7ffe656eeaec
1 0x7ffe656eeaec
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 4: MyT&& v4 = fn()              NOT OK.
constructor call          0x7ffe656eeaf0
1 0x7ffe656eeaf0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5: MyT&& v5 = std::move(fn())   NOT OK.
constructor call          0x7ffe656eeaf4
destructor call           0x7ffe656eeaf4
1 0x7ffe656eeaf4
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 6: end
destructor call           0x7ffe656eeaf4
destructor call           0x7ffe656eeaf0
destructor call           0x7ffe656eeae8
destructor call           0x7ffe656eeae0

Вопросы к шагам 3, 4, 5.

3
Что здесь происходит?
Почему это не то же самое, что и в шаге 2?
Как так получилось, что по одному и тому же адресу 0x7ffe656eeaec мы звонили в constructor - destructor - print цепочку и не получили segfault?\

4 и 5
Откуда была выделена память?
Как мы получили 2 разных адреса экземпляра MyT из нединамических распределений в fn() вызовах?
Это зарезервированная память для статических вызовов? Или они тогда размещаются в стеке? Например, компилятор видит, что это локальные переменные, и резервирует области памяти программы для таких вещей. Но я изучил дизассемблирование, и это был обычный вызов функции. Я не мог проверить это на динамических данных, так как я не могу создать массив rvalues.\

Неважно, какие флаги оптимизации я устанавливаю, результат один и тот же. Но вот команда сборки:

g++ -std=c++17 -g -Wall -O3 -pedantic

Обновление 1: уменьшен пример кода.

Я бы удалил части кода, не связанные с вопросом.

HolyBlackCat 19.02.2023 18:51
and didn't get segfault? потому что это называется неопределенным поведением по какой-то причине.
tkausl 19.02.2023 18:52

В настоящее время этот вопрос включает в себя несколько вопросов в одном. Он должен сосредоточиться только на одной проблеме.

Drew Dormann 19.02.2023 18:56

Я не уверен, почему вы думаете, что локальная переменная не может иметь другой адрес? Если вы вызываете его в другой функции, то они наверняка (скорее всего) будут другими.

apple apple 19.02.2023 19:08

Кстати, вопрос на самом деле не имеет ничего общего с семантикой движений. Если вы замените std::move функцией, которая просто возвращает свой аргумент в качестве ссылки, и замените все ссылки rvalue ссылками const lvalue, вы вернетесь в пред-C++11, прежде чем переместить семантику с тем же поведением. Речь идет о времени жизни временных объектов. См. godbolt.org/z/4MsacE643.

user17732522 19.02.2023 19:13

Краткий ответ, поскольку вопрос сейчас закрыт: временные объекты живут до конца полного выражения, в котором они проявляются, если только они не связаны немедленно, а не через вызов функции, со ссылкой, и в этом случае их время жизни продлевается до конца области ссылки. (Немедленно не совсем правильно, разрешены определенные промежуточные выражения, но не вызовы функций. Однако ссылки в параметрах функций также не уменьшат время жизни.)

user17732522 19.02.2023 19:20
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
  1. Это неопределенное поведение. Вы передаете временный объект в качестве аргумента параметра std::move(), параметр является локальной переменной std::move(). Функция возвращает ссылку на локальную переменную, возвращенная ссылка на локальную переменную не продлевает жизнь объекта. Это неопределенное поведение.

Вопрос был отредактирован. Исходный вопрос задавался о 10-м случае. Сейчас это 3.

  1. В порядке. && продлевает срок службы временного объекта.
  2. Та же проблема 3.

Разве 3 и 5 не равны случаю 4? Я имею в виду, с точки зрения времени жизни варов, все они временные. На 3 создается и перемещается (но перемещать некуда). В 4 он создается как локальная переменная вызова fn и должен быть уничтожен, но все же я смог получить к нему доступ. В 5 так же, как и в 4, это все же временно. Почему тогда && не продлевает срок службы для 3 и 5?

Artsiom Miksiuk 19.02.2023 19:17

Нет, 4 — это совсем другой случай. && с временным объектом — нормально, && с возвращаемой локальной переменной — UB. fn не создает и не возвращает локальную переменную. Пожалуйста, покажите имя этой локальной переменной. Жизнь локальной переменной не может быть продлена, потому что локальная переменная находится в стеке или регистре. Ссылка не может ссылаться на разрушенный фрейм локального стека или регистр.

273K 19.02.2023 19:19

Ну, он не возвращает переменную, но создает локальный объект MyT и сразу же возвращает его. Эти локальные объекты также создаются в стеке, не так ли? Какая тогда разница? @user17732522 в комментариях к вопросу написал про немедленную привязку, о которой я не в курсе. Это то, о чем вы тоже говорите?

Artsiom Miksiuk 19.02.2023 19:23

Освежить ваши знания о RVO. В старых компиляторах вы увидите вызов конструктора копирования и ссылку на нелокальную копию.

273K 19.02.2023 19:26

Немедленная привязка означает привязку только к созданному объекту. Привязка к возвращаемому объекту не является непосредственной привязкой. Если временное привязывается сразу, все в порядке.

273K 19.02.2023 19:37

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