Почему объект, возвращаемый по значению, имеет тот же адрес, что и объект внутри метода?
Проблема: Почему адреса переменных a
и b
совпадают?
Я столкнулся с интересным поведением в моем коде на C++, где две переменные a
и b
, каждая из которых объявлена в разных контекстах (функции foo
и main
), имеют одинаковый адрес в памяти. Вот код, который я использовал:
#include <stdio.h>
#include <array>
#include <vector>
std::vector<int> foo() {
int i;
std::vector<int> a(100);
printf("%p, %p, %p\n", &i, &a, &(a[0]));
return a;
}
int main() {
int i;
std::vector<int> b = foo();
printf("%p, %p, %p\n", &i, &b, &(b[0]));
}
Вывод программы выглядит следующим образом:
0x7ffee28d28ac, 0x7ffee28d28f0, 0x7ff401402c00
0x7ffee28d290c, 0x7ffee28d28f0, 0x7ff401402c00
Как видно, адрес переменной a
(внутри функции foo
) и адрес переменной b
(в функции main
) совпадают. Это поведение кажется странным и вызывает вопросы. Неужели это какая-то оптимизация компилятора, связанная с "кросс-стековыми кадрами"? Любопытно, что результат оказывается одинаковым, даже если я компилирую с флагом -O0
.
Есть ли объяснение этому явлению? Как это связано со стековой памятью и жизненным циклом переменных?
3 ответ(ов)
Это связано с устранением копий (copy elision) и оптимизацией именованных возвращаемых значений (NRVO). Функция foo
возвращает именованный объект a
. Поэтому компилятор не создает локальный объект и не возвращает его копию, а создает объект прямо в том месте, где его использует вызывающий код. Вы можете узнать больше об этом на сайте https://en.cppreference.com/w/cpp/language/copy_elision. С C++17 применение RVO является обязательным, тогда как NRVO – нет, но, похоже, ваш компилятор поддерживает это даже с параметром -O0
.
Обратите внимание, что даже без элиминации копирования (обязательной или нет), уже возможно, что адреса двух объектов будут одинаковыми, поскольку их времена жизни не пересекаются.
Как объяснили другие, это происходит из-за оптимизации копирования (copy elision). Вы можете отключить эту оптимизацию с помощью компилятора g++ следующим образом:
g++ -fno-elide-constructors main.cpp
где main.cpp
содержит ваш код.
Теперь переменные a
и b
будут иметь разные адреса, но a[0]
и b[0]
будут иметь одинаковый адрес из-за семантики перемещения (move semantics).
Какова разница между 'typedef' и 'using'?
Когда действительно стоит использовать noexcept?
Возможно ли вывести тип переменной в стандартном C++?
Почему `std::initializer_list` не поддерживает оператор подиндексации?
Можно ли вручную определить преобразование для класса enum?