[C++] distruttori e memoria stack

Messaggioda anto_zoolander » 01/02/2020, 14:24

Ciao!
prima di esprimere la mia perplessità scrivo un frammento di codice(funzione)

Codice:
class Base{
    int x;
};


Base& operator+(Base A,Base B){

    Base* sum_Base{new Base};
    sum_Base.x=A.x+B.x;
    return *sum_Base;
}


quando il programma esce dalla funzione vengono distrutte le variabili locali A,B
la memoria allocata, con la prima istruzione nella funzione, rimane tale anche dopo l'uscita dalla funzione.

domanda uno: il puntatore sum_Base viene distrutto lasciando intatto l'oggetto a cui punta?

domanda due: se al posto di passare A,B avessi passato &A e &B non appena il programma sarebbe uscito dall'ambito della funzione sarebbero stati distrutti gli oggetti A,B?

a tal proposito ho pensato che vengono distrutti solo gli elementi presenti sulla memoria stack e che quindi
1- il puntatore viene distrutto
2- gli oggetti A,B non vengono distrutti perchè non sono sullo stack.
Error 404
Avatar utente
anto_zoolander
Moderatore
Moderatore
 
Messaggio: 4334 di 9002
Iscritto il: 06/10/2014, 15:07
Località: Palermo

Re: [C++] distruttori e memoria stack

Messaggioda apatriarca » 01/02/2020, 17:53

La memoria allocata da new Base non viene deallocata automaticamente, ma deve essere deallocata manualmente dal codice che utilizza la tua funzione. Gli oggetti A e B sono nello stack essendo variabili temporanee allocate quando la funzione viene richiamata. È un pessimo esempio sinceramente in quanto non è affatto chiaro che il valore restituito è stato allocato dinamicamente.
apatriarca
Moderatore
Moderatore
 
Messaggio: 5350 di 10435
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [C++] distruttori e memoria stack

Messaggioda vict85 » 01/02/2020, 18:05

Concordo, non è una buona prassi passare un oggetto allocato dinamicamente come riferimento invece che come puntatore. Non è inoltre molto comune allocare dinamicamente dentro l'operatore di somma1.

Per farti capire il problema, supponiamo che l'operatore moltiplicazione si comporti nello stesso modo e che si faccia qualcosa come:
Codice:
auto result = a * b + c * d;

Questo codice apparentemente innocuo e valido produrrebbe ben 3 memory leaks!

Note

  1. Tra l'altro, l'operatore di somma dovrebbe, a mio avviso, sempre ritornare la classe e non un riferimento.
vict85
Moderatore
Moderatore
 
Messaggio: 10054 di 19253
Iscritto il: 16/01/2008, 00:13
Località: Berlin

Re: [C++] distruttori e memoria stack

Messaggioda vict85 » 01/02/2020, 18:27

Comunque se vuoi capire che succede puoi anche compilare ed eseguire qualcosa come questo:

Codice:
#include <iostream>

struct Base
{
    Base( )
        : x( 0 )
    {
        std::cout << "costruttore default\n";
    }

    explicit Base( int x )
        : x( x )
    {
        std::cout << "costruttore con valore " << x << std::endl;
    }

    Base( const Base& B )
        : x( B.x )
    {
        std::cout << "costruttore copia con valore " << x << std::endl;
    }

    ~Base( )
    {
        std::cout << "distruttore elemento con valore " << x << std::endl;
    }

    int x;
};

Base&
operator+( Base A, Base B )
{
    std::cout << "Operazione iniziata\n";
    Base* sum_Base{new Base};
    sum_Base->x = A.x + B.x;
    std::cout << "Operazione finita\n";
    return *sum_Base;
}

int
main( )
{
    auto A = Base{3};
    auto B = Base{5};
    std::cout << "Prime allocazioni\n";
    auto* C = &( A + B );  // per renderlo valido
    std::cout << "Operazione conclusa\n";
    std::cout << "Il risultato e' " << C->x << std::endl;
    delete C;
}
vict85
Moderatore
Moderatore
 
Messaggio: 10055 di 19253
Iscritto il: 16/01/2008, 00:13
Località: Berlin

Re: [C++] distruttori e memoria stack

Messaggioda anto_zoolander » 02/02/2020, 12:52

Pardon per l'esempio, non è un qualcosa che ho implementato.
Vi riporto quello che ho fatto realmente(mi sto esercitando quindi provo ad inventarmi qualcosa).

@Vict
ho ritornato un riferimento perché onestamente non sapevo come tenermi la memoria allocata visto che opero con zone di memoria.

Testo nascosto, fai click qui per vederlo
anzi se mi deste qualche esercizio tosto di python/c++ ve ne sarei grato.



definizioni che possono essere utili
Testo nascosto, fai click qui per vederlo
Codice:
template<class T> class List{
    Node<T>* begin_p;
    Node<T>* end_p;
    size_t sz;
public:
    List(List<T>& list);
    List(std::initializer_list<T> lst); //initializer list
    explicit List(); //default constructor
    explicit List(size_t n); // n new nodes
    ~List(); // free all node
   
    void append(T new_data); // new node after end
    void insert(T new_data,size_t pos); //new data in pos
    void remove(size_t pos); // delete node
    void print() const; //print list
    inline size_t size() const; // return length
    bool operator==(List& lst);
    T& operator[](size_t n);
};


i nodi sono oggetti che contengono informazioni di vario tipo

Codice:
template<class T> struct Node{
    T data;
    Node* l_link; //from begin to end
    Node* r_link; //from end to begin
   
    Node(); // ++istance
    ~Node(); // --istance
    inline static size_t get_istance();
private:
    static size_t istance;
};


il distruttore della lista ha la seguente definizione
si occupa di deallocare ogni nodo quando si deve chiudere la lista.

Codice:
template<class T> List<T>::~List(){
    end_p=nullptr;
    Node<T>* node_to_delete{};
    while(begin_p!=nullptr){
        node_to_delete=begin_p;
        begin_p=begin_p->l_link;
        delete node_to_delete;
    }
}


l'esempio era praticamente il seguente
l'operatore rimuove dalla lista di sinistra gli elementi comuni con la lista di destra

Codice:
template<class T> List<T>& operator-(List<T> a_List,List<T> b_List){
   
    List<T>* diff_List{new List<T>{a_List}};
   
    for(size_t i{a_List.size()} ; i>=1; --i){
        size_t j{1};
        while(j<=b_List.size() and a_List[i]!=b_List[j] )
            ++j;
       
        if(j<=b_List.size())
            (*diff_List).remove(i);
    }
   
    return (*diff_List);
}

quello che viene ritornato è il riferimento ad una lista
in questo caso quando si uscirà dall'ambito dell'oggetto ritornato la memoria verrà deallocata.

per esempio se compilo questo codice e setto un breakpoint alla riga 12, viene evocato il distruttore e dealloca la memoria.

Codice:
 1 #include "Listdef.cpp"
 2
 3 int main(void) {
 4
 5     List<int> A{1,2,3};
 6     List<int> B{3,7};
 7   
 8     {
 9         List<int> C{B-A};
10     }
11   
12 }


Quando si esce dall'ambito di operator- vengono deallocate la zona di memoria occupata dal puntatore e la copia dei due oggetti i quali sono allocata sullo stack, diversamente accade per l'oggetto puntato che è allocato dinamicamente e quindi persiste uscendo dalla funzione, fino a quando non si esce dall'ambito di definizione dell'oggetto ritornato. E' corretto?

invece per il seguente frammento

Codice:
 1 #include "Listdef.cpp"
 2
 3 int main(void) {
 4
 5     List<int> A{1,2,3};
 6     List<int> B{3,7};
 7     List<int> C{};
 8   
 9     {
10         C=B-A;
11    }
12   
13 }


alla riga 11 l'oggetto la memoria non viene deallocata poiché il riferimento è passato ad un oggetto con scope più ampio.

PS: in questo caso le operazioni che vengono fatte per copiare gli oggetti all'interno della funzione sono parecchie e potrebbero essere allocati parecchi byte di memoria: non sarebbe meglio passare le liste per riferimento evitandosi tutte queste operazioni?
Error 404
Avatar utente
anto_zoolander
Moderatore
Moderatore
 
Messaggio: 4335 di 9002
Iscritto il: 06/10/2014, 15:07
Località: Palermo

Re: [C++] distruttori e memoria stack

Messaggioda apatriarca » 02/02/2020, 16:04

Nel codice che hai scritto c'è una lista temporanea che non viene deallocata. Ti consiglio di fare uso di unique_ptr o shared_ptr per gestire la memoria se hai dubbi. Fai inoltre uso di std::vector invece di scriverti una tua versione.
apatriarca
Moderatore
Moderatore
 
Messaggio: 5351 di 10435
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [C++] distruttori e memoria stack

Messaggioda apatriarca » 04/02/2020, 01:54

anto_zoolander ha scritto:@Vict
ho ritornato un riferimento perché onestamente non sapevo come tenermi la memoria allocata visto che opero con zone di memoria.

[...]

l'esempio era praticamente il seguente
l'operatore rimuove dalla lista di sinistra gli elementi comuni con la lista di destra

Codice:
template<class T> List<T>& operator-(List<T> a_List,List<T> b_List){
   
    List<T>* diff_List{new List<T>{a_List}};
   
    for(size_t i{a_List.size()} ; i>=1; --i){
        size_t j{1};
        while(j<=b_List.size() and a_List[i]!=b_List[j] )
            ++j;
       
        if(j<=b_List.size())
            (*diff_List).remove(i);
    }
   
    return (*diff_List);
}

quello che viene ritornato è il riferimento ad una lista
in questo caso quando si uscirà dall'ambito dell'oggetto ritornato la memoria verrà deallocata.

per esempio se compilo questo codice e setto un breakpoint alla riga 12, viene evocato il distruttore e dealloca la memoria.

Codice:
 1 #include "Listdef.cpp"
 2
 3 int main(void) {
 4
 5     List<int> A{1,2,3};
 6     List<int> B{3,7};
 7   
 8     {
 9         List<int> C{B-A};
10     }
11   
12 }


Quando si esce dall'ambito di operator- vengono deallocate la zona di memoria occupata dal puntatore e la copia dei due oggetti i quali sono allocata sullo stack, diversamente accade per l'oggetto puntato che è allocato dinamicamente e quindi persiste uscendo dalla funzione, fino a quando non si esce dall'ambito di definizione dell'oggetto ritornato. E' corretto?

invece per il seguente frammento

Codice:
 1 #include "Listdef.cpp"
 2
 3 int main(void) {
 4
 5     List<int> A{1,2,3};
 6     List<int> B{3,7};
 7     List<int> C{};
 8   
 9     {
10         C=B-A;
11    }
12   
13 }


alla riga 11 l'oggetto la memoria non viene deallocata poiché il riferimento è passato ad un oggetto con scope più ampio.

PS: in questo caso le operazioni che vengono fatte per copiare gli oggetti all'interno della funzione sono parecchie e potrebbero essere allocati parecchi byte di memoria: non sarebbe meglio passare le liste per riferimento evitandosi tutte queste operazioni?

Tutto quello che hai detto è in realtà sbagliato. L'unica differenza tra i due casi è che l'oggetto \(C\) ha una vita diversa per cui la sua memoria verrà deallocata in momenti diversi. Tuttavia la vita degli oggetti temporanei allocati nella funzione non cambia. La lista allocata dinamicamente in particolare non viene deallocata.

Il metodo probabilmente "corretto" sarebbe stato implementando un "move constructor" e scrivendo:
Codice:
template<class T> List<T> operator-(const List<T>& a_List, const List<T>& b_List){

    List<T> diff_List = a_List;

    // ...

    return std::move(diff_List);
}

Nota che le due liste passate come argomento sono passate per riferimento e non per valore. Le stai inutilmente copiando.

Il codice interno è comunque incredibilmente inefficiente (qualcosa come \(O(n^3)\) probabilmente), ma non l'ho calcolata più di tanto bene. In linea di massima, questo NON è il modo in cui vuoi usare una lista concatenata.

anto_zoolander ha scritto:anzi se mi deste qualche esercizio tosto di python/c++ ve ne sarei grato.

Non sono sicuro che cosa intendi. Vuoi che inventiamo dei problemi da risolvere in C++ o Python? Hai dato un occhiata a siti come Codility? Vuoi qualcosa più sull'organizzazione del codice?
apatriarca
Moderatore
Moderatore
 
Messaggio: 5356 di 10435
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [C++] distruttori e memoria stack

Messaggioda anto_zoolander » 04/02/2020, 15:09

Ciao :-D

Diciamo che sto smanettando con c++ in autonomia e senza troppe pretese sulla efficienza per adesso.
Probabilmente se volessi provare a farlo meglio eviterei la classe Node e lavorerei con una sola classe.

apatriarca ha scritto: l'oggetto C ha una vita diversa per cui la sua memoria verrà deallocata in momenti diversi. Tuttavia la vita degli oggetti temporanei allocati nella funzione non cambia. La lista allocata dinamicamente in particolare non viene deallocata

Ho messo quel blocco in mezzo al nulla per chiarirmi un dubbio: un oggetto allocato dinamicamente all'interno di una funzione, e ritornato da essa il suo riferimento, ha lo scope della variabile che 'prende' questo oggetto.

apatriarca ha scritto: Nota che le due liste passate come argomento sono passate per riferimento e non per valore. Le stai inutilmente copiando

Nel messaggio precedente, alla fine, avevo proprio chiesto se non fosse meglio passarle per riferimento proprio al fine di evitare sprechi.

apatriarca ha scritto: Non sono sicuro che cosa intendi. Vuoi che inventiamo dei problemi da risolvere in C++ o Python? Hai dato un occhiata a siti come Codility? Vuoi qualcosa più sull'organizzazione del codice?

Non conosco Codiliity, adesso lo guardo.
Mi interessano esercizi che mi allenino ad, appunto, organizzare meglio il codice e allo stesso tempo che siano "complicati".

Ovviamente non chiederei di crearmi esercizi, però se ne avete a disposizione... :lol:
Error 404
Avatar utente
anto_zoolander
Moderatore
Moderatore
 
Messaggio: 4337 di 9002
Iscritto il: 06/10/2014, 15:07
Località: Palermo

Re: [C++] distruttori e memoria stack

Messaggioda apatriarca » 04/02/2020, 17:27

anto_zoolander ha scritto:Ho messo quel blocco in mezzo al nulla per chiarirmi un dubbio: un oggetto allocato dinamicamente all'interno di una funzione, e ritornato da essa il suo riferimento, ha lo scope della variabile che 'prende' questo oggetto.

Non ce l'ha. Per vederlo puoi considerare il seguente codice:
Codice:
#include <iostream>

struct A {
    A() { std::cerr << "Create A\n"; }
    A(const A& a) { std::cerr << "Copy A\n"; }
    A(A&& a) { std::cerr << "Move A\n"; }
    ~A() { std::cerr << "Destroy A\n"; }
};

A& operator-(const A& a1, const A& a2)
{
    A* result = new A;
    return (*result);
}

int main(void)
{
    A first;
    A second;
    A third{second - first};
}

Se provi ad eseguirlo ottieni
Codice:
Create A
Create A
Create A
Copy A
Destroy A
Destroy A
Destroy A

Vengono cioè creati/allocati 4 oggetti di tipo A ma solo 3 vengono distutti/deallocati.
apatriarca
Moderatore
Moderatore
 
Messaggio: 5358 di 10435
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [C++] distruttori e memoria stack

Messaggioda anto_zoolander » 04/02/2020, 19:05

Questo perché non viene deallocato l'oggetto definito all'interno del costruttore di copia, giusto?

Vediamo se ho capito:

Se
Codice:
 A third{second-first} ;

Fosse stato
Codice:
 A& third{second - third} ;

Si sarebbero creati e deallocati esattamente 3 oggetti?
Error 404
Avatar utente
anto_zoolander
Moderatore
Moderatore
 
Messaggio: 4338 di 9002
Iscritto il: 06/10/2014, 15:07
Località: Palermo

Prossimo

Torna a Informatica

Chi c’è in linea

Visitano il forum: Nessuno e 1 ospite