• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Inteligentne wskaźniki, delete

VPS Starter Arubacloud
0 głosów
434 wizyt
pytanie zadane 12 sierpnia 2017 w C i C++ przez niezalogowany

Pogooglowałem trochę i dowiedziałem się, że inteligentne wskaźniki mogą tak jak się spodziewałem wskazywać na tablicę, a nie tylko pojedynczą zmienną. Tylko nie jestem pewien czy faktycznie jest później zwalniana cała tablica czy tylko jedna zmienna, bo angielskojęzyczne fora mówią różnie na ten temat.

Zauważyłem, że ten kod:

#include <memory>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector <int> adresy;

    {
        unique_ptr <int[]> ptr(new int[4]);
        for(int i=0; i<4; i++)
            ptr[i] = i*3;

        for(int i=0; i<4; i++)
        {
            cout << ptr[i] << endl;

            cout << (int)&ptr[i] << endl;

            adresy.push_back((int)&ptr[i]);
        }

        for(int i=0; i<4; i++)
        {
            int* wsk = reinterpret_cast < int* > (adresy[i]);
            cout << *wsk << endl;
        }
    }

    for(int i=0; i<4; i++)
    {
        int* wsk = reinterpret_cast < int* > (adresy[i]);
        cout << *wsk << endl;
    }

   return 0;
}

działa tak samo jak ten:

#include <memory>
#include <iostream>
#include <vector>
#include "text.h"

using namespace std;

int main()
{
    vector <int> adresy;

        int *ptr = new int[4];
        for(int i=0; i<4; i++)
            ptr[i] = i*3;

        for(int i=0; i<4; i++)
        {
            cout << ptr[i] << endl;

            cout << (int)&ptr[i] << endl;

            adresy.push_back((int)&ptr[i]);
        }

        for(int i=0; i<4; i++)
        {
            int* wsk = reinterpret_cast < int* > (adresy[i]);
            cout << *wsk << endl;
        }

    delete ptr;

    for(int i=0; i<4; i++)
    {
        int* wsk = reinterpret_cast < int* > (adresy[i]);
        cout << *wsk << endl;
    }

    return 0;
}

Dwie pierwsze zmienne przechowują jakieś dziwne wartości po użyciu operatora delete jawnie (II przypadek) lub po użyciu przez unique_ptr operatora delete/delete[] (nie wiem czy na pewno jest wywoływany delete[] czy delete i czy w ogóle któryś jest) a w dalszych komórkach są nadal sensowne wartości. Ktoś iwe czemu tak jest i czy mogę spokojnie używać I sposobu bez ryzyka wycieku pamięci?

3 odpowiedzi

+1 głos
odpowiedź 12 sierpnia 2017 przez unknown Nałogowiec (39,560 p.)
To co robisz to UB. Odwołujesz się do już zwolnionej pamięci. Równie dobrze mogłoby wypisać "Hello world"
komentarz 12 sierpnia 2017 przez niezalogowany
Ale w sumie dziwne, że w tej już zwolnionej pamięci dwie pierwsze komórki przechowują jakieś śmieci, a wszystkie pozostałe mają nadal sensowne wartości, i to niezależnie od tego jak duża jest tablica. To na pewno przypadek?
komentarz 12 sierpnia 2017 przez unknown Nałogowiec (39,560 p.)
Jak pisałem, to jest UB
komentarz 12 sierpnia 2017 przez niezalogowany

Zrobiłem to UB żeby sprawdzić czy pamięć faktycznie jest zwalniana. Czyli mówisz, że mogę stosować taki zapis

unique_ptr <int[]> ptr(new int[4]);

i unique_ptr wywoła operator delete[] i wszystko będzie w porządku, bez żadnych wycieków? 

Niby tak by z tego wynikało

http://en.cppreference.com/w/cpp/memory/unique_ptr

komentarz 12 sierpnia 2017 przez unknown Nałogowiec (39,560 p.)

Zrobiłem to UB żeby sprawdzić czy pamięć faktycznie jest zwalniana.

To raczej dość słaby sposób i oznaka braku wiedzy. Mogłeś użyć np. valgrinda

unique_ptr wywoła operator delete[]

Jeśli nie podasz innego deletera to tak. 

cpprefernce możesz spokojnie ufać

komentarz 12 sierpnia 2017 przez m4sk1n Pasjonat (16,750 p.)
Kacper, to, że część adresów zawiera nadal sensowne wartości oznacza, że nie zostały one jeszcze niczym zastąpione ;)
komentarz 12 sierpnia 2017 przez niezalogowany

BTW Można jakoś te wskaźniki przesuwać po elementach tablicy? Bo chciałbym przerobić stary program, gdzie używam new i delete na taki, gdzie będę miał unique_ptr i mam tam instrukcje:

Word *tab_slowek;
tab_slowek = new Word [slowek];
Word *wskaznik = tab_slowek;
// some code
for(int i=0; i<slowek; i++)
{
    // some code
    wskaznik++;
}

I nie wiem czy można ten fragment związany z przesuwaniem wskaźnika jakoś załatwić, tak żeby ten wskaźnik też był inteligentny.

Niby mogę zrobić tak:

int *wska;
wska = ptr.get();

ale wtedy wska nie jest inteligentny i muszę pamiętać o zwolnieniu pamięci. Po wielu miesiącach przysiadłem do tej aplikacji i zauważyłem, że tablice zwalniałem, ale o zwykłych wskaźnikach to nie pamiętałem niestety.

komentarz 12 sierpnia 2017 przez unknown Nałogowiec (39,560 p.)
Dlaczego nie użyjesz operatora [] ?

get zwraca wskaźnik ale nie oddaje ownership'u więc nie musisz się przejmować ręcznym zwalnianiem pamięci
+1 głos
odpowiedź 12 sierpnia 2017 przez mokrowski Mędrzec (155,460 p.)

W bibliotece standardowej C++11 i nowszych, przewidziano użycie tablic z unique_ptr dostarczając poprawną implementację funkcji usuwającej tę tablicę. Tablica z unique_ptr będzie poprawnie usuwana bo jest specjalizacja konstruktora dla takiego typu danych.

Dla shared_ptr nie przewidziano implementacji obsługi tablicy. Wtedy wystarczy użyć 2 argumentu konstruktora np. używając zdefiniowanej dla unique_ptr funkcji usuwającej. 

Np. tak:

std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());

 

komentarz 12 sierpnia 2017 przez niezalogowany
A można zrobić to co napisałem w ostatnim komentarzu przy odpowiedzi unknown?
komentarz 12 sierpnia 2017 przez mokrowski Mędrzec (155,460 p.)

Oczywiście. Np. tak:

#include <memory>
#include <iostream>

constexpr static size_t TAB_SIZE = 10;

int main() {
	std::shared_ptr<int> my_table(new int[TAB_SIZE], std::default_delete<int[]>());
	
	// Simple fill table... 
	for(auto i = 0U; i < TAB_SIZE; ++i) {
		my_table.get()[i] = i * 42;
	}

	// Simple show table... 
	for(auto i = 0U; i < TAB_SIZE; ++i) {
		std::cout << my_table.get()[i] << ' ';
	}
	std::cout << std::endl;
}

Albo tak... 

#include <memory>
#include <iostream>

constexpr static size_t TAB_SIZE = 10;

int main() {
	std::shared_ptr<int> my_table(new int[TAB_SIZE], std::default_delete<int[]>());
	
	// Simple fill table... 
	for(auto i = 0U; i < TAB_SIZE; ++i) {
		*(my_table.get() + i) = i * 42;
	}

	// Simple show table... 
	for(auto i = 0U; i < TAB_SIZE; ++i) {
		std::cout << *(my_table.get() + i) << ' ';
	}
	std::cout << std::endl;
}

Myślę że lepsze jest 1 podejście :-)

komentarz 12 sierpnia 2017 przez niezalogowany
No tak, można też zrobić samo my_table[i] bez używania get() i będzie działać. Ale mi chodziło o zrobienie tego dodatkowego wskaźnika pokazującego na tablicę i żeby można go było przesuwać po kolejnych indeksach. Żeby dla dużych tablic dużych obiektów odczyt był szybszy, bo program nie musiałby za każdym razem zaglądać do "spisu treści" jak mówił Zelent czy Grębosz tylko mógł przewijać kolejne strony. Ale jeżeli zwolnienie z obowiązku zwalniania pamięci niesie ze sobą taki koszt, że się nie da wtedy tego zrobić to już trudno, wtedy nie będę dalej kombinował.

EDIT

To drugie to chyba to o co mi chodzi?
komentarz 12 sierpnia 2017 przez mokrowski Mędrzec (155,460 p.)

A kto Ci powiedział że zagląda? Kompilator w miejscu get() wstawi po prostu wskaźnik i będzie bazował na arytmetyce wskaźnika. To przecież robią nawiasy kwadratowe. Równie dobrze możesz zrobić shared/unique ptr do std::array<...> lub std::vector<...> a nie "bawić się żyletką i zapałkami" z tablicą. Im szybciej jej zaniechasz, tym szybciej uwolnisz się od takich zabaw jakie pokazałem. Popełniasz także błąd że kompilator wygeneruje bardziej wydajną konstrukcje jeśli będzie ona wyglądała jakoś tak: *(my_table.get() + i) bo "ma niby być szybsza od":  my_table.get()[i] . Nie. Nie jest szybsza. Dla (dobrego) kompilatora to obojętne a Ty możesz się pomylić we wpisywaniu tych gwiazdek.

Automatyczne zwalnianie pamięci nie jest aż takim kosztem którego nie chcesz ponieść. Tylko nie upieraj się na tablicach lub innych niskopoziomowych konstrukcjach. Trzeba je stosować wtedy kiedy to usprawiedliwione np. wymogami umieszczania w ciągłej pamięci na bardzo niskim poziomie abstrakcji a nie dlatego że "może będzie szybciej"... najczęściej nie jest... 

Np. co Ci szkodzi robić tak?

#include <memory>
#include <array>
#include <iostream>

constexpr static size_t TAB_SIZE = 10;

int main() {
	using table_t = std::array<int, TAB_SIZE>;
	std::shared_ptr<table_t> my_table(new table_t());
	
	// Simple fill table... 
	for(auto i = 0U; i < TAB_SIZE; ++i) {
		(*my_table)[i] = i * 42;
	}

	// Simple show table... 
	for(const auto& val: *my_table) {
		std::cout << val << ' ';
	}
	std::cout << std::endl;
}

 

komentarz 12 sierpnia 2017 przez criss Mędrzec (172,590 p.)

Żeby dla dużych tablic dużych obiektów odczyt był szybszy, bo program nie musiałby za każdym razem zaglądać do "spisu treści" 

operator[] (dla wskaźników) w teorii realizuje dokładnie to samo, co "jawne" przesuwanie wskaźnika. W praktyce często nawet się okazuje, że korzystanie z operatora[] jest szybsze.

Ale jeżeli zwolnienie z obowiązku zwalniania pamięci niesie ze sobą taki koszt, że się nie da wtedy tego zrobić to już trudno...

Tak jak ci napisał unknown - prosząc o przetrzymywany w inteligentnym wskaźniku "raw pointer" metodą get() nikogo nie zwalniasz z żadnego obowiązku. Obiekt unique_ptr nadal trzyma adres, który zwolni przy destrukcji (pamięć zwolni ofc :D). Zwolnienie przez ciebie "ręcznie" nawet by było zdecydowanie niewskazane, bo unique_ptr będzie próbował wtedy zwolnić już zwolnioną pamięć.
Także tak - możesz sobie stworzyć ten dodatkowy wskaźnik i absolutnie nie martwić się o zwalnianie pamięci.

komentarz 12 sierpnia 2017 przez niezalogowany
edycja 12 sierpnia 2017

Faktycznie korzystanie z operatora [] jest szybsze.

#include <memory>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

int main()
{
    vector <int> adresy;

    clock_t start, stop;
    unique_ptr <int[]> table(new int[200000000]);

    //////////////////////////////////////////////////
    start = clock();
    for(int i=0; i<200000000; i++)
    {
        table[i] = i*2;
        table[i] -= 10;
    }
    stop = clock();
    cout << (double)(stop - start)/CLOCKS_PER_SEC << endl;
    //////////////////////////////////////////////////

    //////////////////////////////////////////////////
    start = clock();
    for(int i=0; i<200000000; i++)
    {
        *(table.get()+i) = i*2;
        *(table.get()+i) = i*2;
    }
    stop = clock();
    cout << (double)(stop - start)/CLOCKS_PER_SEC << endl;
    //////////////////////////////////////////////////

    //////////////////////////////////////////////////
    start = clock();
    for(int i=0; i<200000000; i++)
    {
        table.get()[i] = i*2;
        table.get()[i] = i*2;
    }
    stop = clock();
    cout << (double)(stop - start)/CLOCKS_PER_SEC << endl;
    //////////////////////////////////////////////////

    return 0;
}

Myślałem, że będzie powolne, ale to "zaglądanie do spisu" i powolność to są w I sposobie

(gdzie jest bezpośrednie odnoszenie się do elementów tablicy).

W II sposobie odnosimy się do elementu poprzez wskaźnik za pomocą operatora*, więc jest szybciej.

A w III sposobie też odnosimy się poprzez wskaźnik tylko za pomocą operatora [] i jest najszybciej.

Dobrze to wszystko zrozumiałem? 

komentarz 12 sierpnia 2017 przez niezalogowany

Zdałem sobie sprawę z bezsensu moich rozważań. Przecież jak tworzę dodatkowy wskaźnik przesuwany po elementach tablicy to nie muszę go usuwać za pomocą delete bo on wskazuje na jakiś element tablicy, która w całości zostanie i tak usunięta, więc nie ma żadnych wycieków.

A w dodatku to jest najszybszy sposób

clock_t start, stop;
    unique_ptr <int[]> table(new int[200000000]);
    int *wsk = table.get();

//////////////////////////////////////////////////
    start = clock();
    for(int i=0; i<200000000; i++)
    {
        *wsk = i*2;
        *wsk -= 10;
        wsk++;
    }
    stop = clock();
    cout << (double)(stop - start)/CLOCKS_PER_SEC << endl;
    //////////////////////////////////////////////////

Jednakże chciałbym się zapytać czy jest to bezpieczne zanim na hurra zacznę z tego korzystać.

0 głosów
odpowiedź 12 sierpnia 2017 przez m4sk1n Pasjonat (16,750 p.)

Podobne pytania

+1 głos
1 odpowiedź 158 wizyt
pytanie zadane 6 czerwca 2019 w C i C++ przez amelia.cpp Obywatel (1,860 p.)
0 głosów
1 odpowiedź 211 wizyt
+1 głos
0 odpowiedzi 176 wizyt
pytanie zadane 7 stycznia 2016 w C i C++ przez 0xf Dyskutant (8,180 p.)

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,853 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...