LINUX.ORG.RU

const ссылка на временный объект

 


0

2

Подскажите, это в современных стандартах законно?

#include <iostream>
struct A {
        void boo() const {
                std::cout<<"I am alive"<<std::endl;
        }
};
 
const A& foo() {
        return A();
}
int main(void) {
        const A& a = foo();
        a.boo();
        return 1;
}
Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод? Если я захочу создать и вывести пооле класса, у меня уже идет segmentation fault. Читал в блоге у алены си пи пи, что все ок типа, но статья старая.


итал в блоге у алены си пи пи, что все ок типа

Разработчики стандартных библиотек добавили в свой workflow санитайзеры в 2013 году, поэтому в стандартных библиотеках C++ меньше уязвимостей чем в Rust…

https://gcc.godbolt.org/z/oTf5EY38r

fsb4000 ★★★★★ ()

Подскажите, это в современных стандартах законно?

Конечно же нет. Ни в современных, ни в каких.

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

А почему ему не отрабатывать?

Читал в блоге у алены си пи пи, что все ок типа, но статья старая.

Не надо читать всякую ламерню.

slovazap ★★★★★ ()

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

Потому что это undefined behavior - т.е. стандарт не определяет, как себя должна при это вести программа.

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

rumgot ★★★★★ ()
Ответ на: комментарий от da17

А почему тогда память где остается реализация методов класса не очищается?

Реализация методов класса - это код, который никогда не изменяется и никак не зависит от времени жизни объектов класса. Если ты имел в виду данные объекта, то зачем это они должны очищаться?

В твоём примере ты вообще не обращаешься к this, и состояние объекта ни на что не влияет. А мог бы даже успешно обратиться к полю, поскольку память где был расположен объект могла остаться доступной программе и даже не быть кем-то переписанной. В другом случае мог бы прочитать мусор, в третьем упасть, в четвёртом это даже не скомпилируется.

slovazap ★★★★★ ()
Последнее исправление: slovazap (всего исправлений: 5)

Да. Const reference продлевает жизнь обьекта.

https://en.cppreference.com/w/cpp/language/lifetime

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details

invy ★★★★★ ()
Последнее исправление: invy (всего исправлений: 1)
Ответ на: комментарий от da17

Ну и что? Ты хочешь разобраться в том, как именно реализовано неопределенное поведение в разных компиляторах? Может будем учиться писать правильно, а не писать неправильно и разбираться, что там будет происходить?

rumgot ★★★★★ ()
Ответ на: комментарий от da17

Ничего непонятно

Я понял, у тебя интернета нет, кроме лора. Ты не можешь перейти на годболт.

const ссылка на временный объект (комментарий)

https://gcc.godbolt.org/z/oTf5EY38r

<source>: In function 'const A& foo()':
<source>:9:16: warning: returning reference to temporary [-Wreturn-local-addr]
    9 |         return A();
      |                ^~~

/app/example.cpp:9:18: runtime error: reference binding to null pointer of type 'const struct A'
/app/example.cpp:13:14: runtime error: member call on null pointer of type 'const struct A'
fsb4000 ★★★★★ ()
Ответ на: комментарий от invy

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details

Но если прочитать про этот reference initialization, то можно выяснить, что:

There are following exceptions to this lifetime rule:

  • a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such return statement always returns a dangling reference.
aldaril_kote ()

ну в общих чертах такой код лишен смысла — ибо к чему возвращать временный объект по ссылке, тем более по константной?

по поводу отработки метода — он принадлежит классу, а не объекту класса, — на него поинтер всегда существует, это просто участок кода, а не стейт объекта — в ассемблере это просто jmp обычно, ну или инлайн.

safocl ★★ ()
Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Ну я плохо ассемблер современный понимаю и по-английски совсем не читаю, у нас в школе и инсте французский был. В общем так писать нельзя, хотя работает. Я понял. Зачем вопрос не стоит, вопрос стоит по-стандарту это ок или запретили уже.

da17 ()
Последнее исправление: da17 (всего исправлений: 1)

На вопрос уже ответили, я не помешаю доп вопросом со «звездочкой»? Что за хитропопую оптимизацию делает вектор #1?

#include <vector>
#include <cstdio>
#include <cstdlib>
using namespace std;


void* operator new(std::size_t sz)
{
    std::printf("global op new called, size = %zu\n", sz);
    if (sz == 0)
        ++sz;
    if (void *ptr = std::malloc(sz))
        return ptr;
    throw std::bad_alloc{};
}

int main()
{
	// #1
	//global op new called, size = 80
	vector<int> v;
	v.reserve(20);
	v = {3, 4};
	
	// #2
	//global op new called, size = 80
	//global op new called, size = 8
	vector<int> v2;
	v2.reserve(20);
	v2 = vector<int>{3, 4};

	return 0;
}

Почему не создаётся промежуточный вектор? Т.е. получается, что второй раз конструктор отрабатывает на уже инициализированном векторе.

anonymous ()
Ответ на: комментарий от anonymous

Почему не создаётся промежуточный вектор?

Открой для себя: https://cppinsights.io/

cppinsights подскажет, что вызываются

constexpr vector& operator=( std::initializer_list<T> ilist );

и

constexpr vector& operator=( vector&& other ) noexcept

https://en.cppreference.com/w/cpp/container/vector/operator%3D

fsb4000 ★★★★★ ()

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

Видите ли, если вы для class создадите объект с помощью malloc вместо new, то код работать будет, но частенько и crash будет в непонятно какие моменты …

anonymous ()
Ответ на: комментарий от anonymous

Почитай про move. У тебя случается аллокация как раз для временного вектора. Но когда ты его присваиваешь переменной, происходит тупо свап указателей. В итоге временный вектор становится пустым (точнее в нем буфер на 20 элементов от старого вектора, но size == 0, а через мгновение вызовется деструктор), а вектор в переменной использует его память.

Даже без move непонятно, зачем старому вектору делать аллокацию, если ты сделал reserve на 20 элементов, а присваиваешь вектор из 2 элементов. Даже если случится обычный copy (в C++ до 11 версии), переаллокация памяти не нужна.

Constexpr для этого совсем не нужен.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

Прежде чем делать move, временный вектор нужно создать, а это аллокация как ни крути, которая отметится в операторе new (но нет). Единственное объяснение (которое мне на ум приходит) - вектор создается в компайлтайме (constexpr), но ГЦЦ со Шлангом этого пока не умеют. Также не ясно - почему в первом случае «constexpr», а во втором нет, я лишь записал там в explicit форме то же самое. К тому же всего одна аллокация и в таком случае:

	int a; 
	cin >> a;
	// #1
	//global op new called, size = 80
	vector<int> v;
	v.reserve(20);
	v = {a};

Никакой constexpr здесь быть не может.

anonymous ()
Ответ на: комментарий от anonymous

Так constexpr вектора пока только лишь мелкомягкие запилили

При чём тут это?

Да не ясно почему в разных случаях (#1, #2) поведение разное.

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

anonymous ()
Ответ на: комментарий от anonymous

Ладно, похоже я не в том месте вопрос задал. Люди вообще не понимают темы компайл тайм строк и векторов, срабатывают лишь какие-то рефлексы на знакомые слова. Мне не вопросы надо задавать, а ликбез проводить.

anonymous ()
Ответ на: комментарий от anonymous

Ладно, похоже я не в том месте вопрос задал.

Ты на https://cppinsights.io/ перешёл или нет?

Что за люди то тут такие, не могут по одной ссылке перейти. Что оп, что ты.

Вот как твой main выглядит в cppinsight.

int main()
{
  vector<int> v = std::vector<int, std::allocator<int> >();
  v.reserve(20);
  v.operator=(std::initializer_list<int>{3, 4});
  vector<int> v2 = std::vector<int, std::allocator<int> >();
  v2.reserve(20);
  v2.operator=(std::vector<int, std::allocator<int> >{std::initializer_list<int>{3, 4}, std::allocator<int>()});
  return 0;
}

Теперь то понятно, почему std::vector во втором случае создаётся, а в первом нет?

И в моём первом ответе нужно было смотреть не на constexpr(он есть у обоих перегрузок оператора =), а на тип который он принимает.

std::initializer_list<T> ilist

и

vector&& other
fsb4000 ★★★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Теперь то понятно, почему std::vector во втором случае создаётся, а в первом нет?

Нет, не понятно. Запись вида

v.operator=(std::initializer_list<int>{3, 4});

«трансформируется» точно в такую же:

v2.operator=(std::vector<int, std::allocator<int> >{std::initializer_list<int>{3, 4}, std::allocator<int>()});

Вызов конвертирующего конструктора с последующем перемещенимем. Первая форма - сокращение от второй, т.к. позволено из отсутствия explicit у конструктора. Единственный фокус провернуть такую - штуку - создать вектор в компайл тайме, а потом лишь скопировать (пройдёт мимо моего оператора new, хотя пример с cin не подтверждает это). Короче не знаю что за фокусы такие происходят.

anonymous ()
Ответ на: комментарий от anonymous

«трансформируется» точно в такую же

Нет. Initializer list это просто два указателя.

https://github.com/microsoft/STL/blob/main/stl/inc/initializer_list#L21-L53

трансформируется в «такую»:

int __temp_arr[2] = {3,4};
std::initializer_list<int> __temp_list(__temp_arr, __temp_arr + 2);
v.operator=(__temp_list);

или такую

int __temp_arr[2] = {3,4};
std::initializer_list<int> __temp_list(__temp_arr, 2);
v.operator=(__temp_list);
fsb4000 ★★★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

Понял, не заметил (честно говоря и не ожидал, что у вектора есть такая сигнатура:

vector& operator=( std::initializer_list<T> ilist );

Какое-то туннельное мышление, инициализер лист плотно ассоциировался с конструктором, никак не с оператором =. Хотя логично проверить было сразу, но шоры …

Спасибо.

anonymous ()
Ответ на: комментарий от fsb4000

Да в rust есть такая проблема, вот тут https://dev.by/news/ob-uyazvimostyakh-standartnoi-biblioteki-rust неплохая статья по теме. Ну и там же есть хорошие доводы почему только по открытым CVE нельзя судить о реальном количестве багов.

anonymous ()
Ответ на: комментарий от anonymous

Я про второй случай. Тут у тебя вполне есть две аллокации.

//global op new called, size = 80
	//global op new called, size = 8

В первом случае вектор вообще не создаётся. У тебя там std::initializer_list, а он создаётся статически. Constexpr для этого не нужен.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от aldaril_kote

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details

Но если прочитать про этот reference initialization, то можно выяснить, что:

There are following exceptions to this lifetime rule:

  • a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such return statement always returns a dangling reference.

А если почитать стандарт, то можно выяснить, что функции не возвращают ссылок. Ни dangling, ни не dangling.

anonymous ()
Ответ на: комментарий от x86-

Нихрена нельзя. Надо понимать одно: в c++ на уровне языка нет никакой мув-семантики. Она реализуется библиотечно, функциями. Для компилятора это просто особый тип ссылки, type&&, к которому автоматически преобразовывается выражение не являющееся именем переменной, и которая может быть отправлена в функцию принимающую type&&. Все кажущиеся логичными предположения о том как должен работать мув являются неверными. Все разговоры о том, что рвалуе ссылка чего-то там продлевает - неверны. Точнее они верны но только в узком смысле, продление жизни происходит только в достаточной степени чтоб хватило на передачу, на выход из функции это не распространяется. Более того, результат вызова функции, возвращающей объект по значению как раз является временным неименованным объектом и потому автоматически кастится к рвалуе ссылке, так что возврат рвалуе значения в принципе не имеет смысла, за исключением передачи назад рвалуе ссылки полученной на входе. Таким образом если тебе хочется создать объект в функции и передать его наружу мувом - просто возвращай по значению.

khrundel ★★★ ()