Wskaźniki czy dobrze to ogarniam?

0
#include "stdafx.h"
#include<iostream>

using namespace std;

int main()
{
	int a = 5; //Przypisuję do a wartośc 5
	int *tomek = new int; //Tworzę wskaźnik, plus przypisuję mu new tak by było miejsce w pamieci do wrzutu danych
	*tomek = a;//Wskaźnik wskazuje teraz na a
	cout <<a<<endl;
	cout << *tomek<<endl;
	a = 7;//Zmieniam wartość a na 7, autoamtycznie zmienia się wartość na jaką wskazuje wskaźnik
	cout << a << endl;
	cout << *tomek << endl;
	//Czy tutaj przed zmianą wskazania, trzeba go wyzerować delete i na nowo wskazać, lub w tym przypadku przypisać jego wartośc, by na coś wskazywał. 
	*tomek = 1;//Przypisuje do wskaźnika wartość 1, po to było to new int, bez tego niemógłbym do niego przypisać wartości bo nie błoby miejsca w pamięci
	cout << a << endl;
	cout << *tomek << endl;
	delete tomek;//Zwalniam pamięć, new int idzie do kosza. 
	//Teraz powinno się chyba przypsiać do wskaźnika Nulla by na coś wskazywał, ps. dzieje się coś jak na nic nie wskazuje po usnięciu jego zawartości new?


	system("pause");
    return 0;
}

Więc, tak jako że zamierzam wejść w klasy, chcę dobrze najpierw ogarniać podstawy, pętle, tablice mam już przećwiczone, znaczy się wiadomo cały czas dochodzą nowe rzeczy, także trudno pisać że się coś ogarnia jak podstawy to nawet nie są podstawy, ale powoli a do przodu, mam takie małe pytanie czy po powyższym kodzie, który w zasadzie nic nie robi, chodzi mi tylko o idee działania wskaźników, czy dobrze to ogarniam, w kwestii zwalniania pamięci zwłaszcza, z góry dzięki za ocenę, pomoc.

0
    int a = 5;
    int *tomek = new int;
    *tomek = a;
    a = 7;//Zmieniam wartość a na 7, autoamtycznie zmienia się wartość na jaką wskazuje wskaźnik

Nie. Niby dlaczego miałaby się zmienić?

0

Faktycznie, gdybym nie dawał new, pewnie by się zmieniło a tak nadal tkwi w pamięci póki jej nie zwolnię, tak jak robię to dalej w kodzie. Ok dzięki.

0

Twój przykład:

using namespace std;

int main()
{
    int a = 5;
    int *tomek = new int;
    *tomek = a; // wskaźnik pobrał wartość a i umieścił ją pod adresem wcześniej otrzymanym przez polecenie new
    cout <<a<<endl;
    cout << *tomek<<endl;
    a = 7; // zmieni się tylko wartość a, tomek zostanie 5
    cout << a << endl;
    cout << *tomek << endl;
    *tomek = 1; // zmieniasz wartość w adresie wskazywanym przez tomka na 1
    cout << a << endl;
    cout << *tomek << endl;
    delete tomek;

}

Jak powinno być (bazując na Twoich komentarzach):

using namespace std;

int main()
{
    int a = 5;
    //int *tomek = new int; nie jest potrzebne w tej wersji
    int *tomek = &a; // wskaźnik wskazuje na a
    cout <<a<<endl;
    cout << *tomek<<endl;
    a = 7; // teraz również zmieni się wartość wskazywana przez wskaźnik
    cout << a << endl;
    cout << *tomek << endl;
    *tomek = 1; // wartość a zmienia się na 1
    cout << a << endl;
    cout << *tomek << endl;
    delete tomek; // nie ma potrzeby, po zakończeniu tego fragmentu kodu miejsce zajmowane przez a i tak zostanie zwolnione
}
1
Zsan napisał(a):
delete tomek; // nie ma potrzeby, po zakończeniu tego fragmentu kodu miejsce zajmowane przez a i tak zostanie zwolnione

}

Brak zwolnienia pamięci przez program przed jego zakończeniem, jest błędem pomimo tego że system operacyjny usuwając proces, posprząta pamięć. Funkcję main() należy traktować tak samo jak i inne funkcje lub zasięgi które pojawić się mogą w programie. Przed funkcją main() oraz po jej zakończeniu można także uruchamiać kod. Jeśli nie posprzątasz po sobie, istnieje ryzyko nieprawidłowego działania tego kodu.

0
   *tomek = 1

Czy przed tym trzeba wyzerować wskaźnik, skoro zmieniam wskazanie wskaźnika i przypisuję mu jakby 1, piszę jakby bo jakbym nie dał new to nie mógłbym tak zrobić. Niby się nie pluje, ale jak to powinno byc robione zgodnie z standardami, przed takim przypisaniem muszę go usunąć z pamięci czy też nie, skoro w następnej linii wartość zostanie zmieniona?

0

@Zsan chyba zapędziłeś się z tym delete.... Takie cos jak pokazałeś zrobi bum... Jak masz system z ochrona pamięci, będzie to tylko bm zamyanego programu, ale nie zawsze tak musi być... Delete używamy tylko wtedy, gdy wczesniej dynamicznie przyznaliśmy pamięć!!!

0

@kaczus rzeczywiście może powinienem był wykomentować tę linijkę by nie było żadnych wątpliwości, że użycie tego delete w drugim wypadku jest niepoprawne

0
hyouka22 napisał(a):

*tomek = a;//Wskaźnik wskazuje teraz na a

Nie, przepisałeś wartość zmiennej a w miejsce wskazywane przez tomek. To co opisuje komentarz robi się tak: tomek = &a;

a = 7;//Zmieniam wartość a na 7, autoamtycznie zmienia się wartość na jaką wskazuje wskaźnik

Nie, ze względu na powyższe.

0
zarazek napisał(a):
hyouka22 napisał(a):
*tomek = a;//Wskaźnik wskazuje teraz na a

Nie, przepisałeś wartość zmiennej a w miejsce wskazywane przez tomek. To co opisuje komentarz robi się tak: tomek = &a;

a = 7;//Zmieniam wartość a na 7, autoamtycznie zmienia się wartość na jaką wskazuje wskaźnik

Nie, ze względu na powyższe.

Ok, rozumiem, to co mam poniżej to kod bez new, tutaj mam to przypisanie. Dzięki za wyjaśnienie.

        int b = 6;
		int *arek;
		arek = &b;
		cout << b << endl;
		cout << *arek << endl;
		cout << &b << endl;
		cout << &*arek << endl;

Teraz mam taki mały random w kwestii tego delete na końcu kodu, stosujemy go, czy nie, bo na logikę po wyjściu z klamry ta zmienna znika, w tym przypadku jest w mainie, ale powiedzmy, że robię funkcję z użyciem tabeli i tam używam wskaźnika, niby po końcu klamry znika zmienna, ale czy mam użyć delete by usnąć wtedy wskaźnik i potem przypisać do niego NULLA czy to zbędne, chodzi mi o to by nie było potem problemów z zwalnianiem pamięci.

2

Teraz mam taki mały random w kwestii tego delete na końcu kodu, stosujemy go, czy nie, bo na logikę po wyjściu z klamry ta zmienna znika, w tym przypadku jest w mainie, ale powiedzmy, że robię funkcję z użyciem tabeli i tam używam wskaźnika, niby po końcu klamry znika zmienna, ale czy mam użyć delete by usnąć wtedy wskaźnik i potem przypisać do niego NULLA czy to zbędne, chodzi mi o to by nie było potem problemów z zwalnianiem pamięci.

Co do zasady, to jest prosta. Jeśli stosujesz gdzieś new, powinno być ono połączone z delete na tym zasobie. Jeśli spróbujesz wywołać delete na zasobie już zwolnionym (np. przez poprzednie delete), będziesz miał zachowanie niezdefiniowane. Większość szanujących się systemów załamie Ci program w tym miejscu ale nie możesz na to liczyć zawsze i wszędzie.
Poprawne zadziałanie delete jest także zapewnione dla wskaźnika ustawionego na NULL (dla C++ lepiej obecnie używać nullptr). Wywołanie delete nie zrobi wtedy po prostu nic.
Przypisanie NULL'a do wskaźnika wskazującego na alokowane dane, nie powoduje że są usuwane. Po prostu tracisz do nich dostęp i jest to przypadek wycieku pamięci!. Ty je po prostu gubisz.
Jeśli opuszczasz zasięg ze zmienną wskazującą na dane alokowane dynamicznie (przez new), owszem zmienna "znika" ale dane pozostają w pamięci i brak wskaźnika do nich (bo przecież zmienna "znikła"), powoduje że ta przestrzeń pamięci nie zostanie ponownie przydzielona. Co innego jeśli zmienna nie zawiera danych alokowanych dynamicznie. Są one wtedy na stosie i wyjście z zasięgu, zwalnia pamięć.

int main() {
	// Uwaga: Kod zawiera zamierzone błędy.
	{
		int * ptr = new int[12]; // Dane alokowane dynamicznie.
	} // <- Tu po wyjściu z zasięgu masz wyciek pamięci. 
	  // Brak dostępu do danych alokowanych dynamicznie które były
	  // dostępne przez ptr.
	  
	{
		int * ptr = new int[12];
		ptr = nullptr;
	} // <- Znów wyciek pamięci bo przypisanie nullptr do wskaźnika 
      // na alokowane dane, nie dealokuje ich!

	{
		int table[12]; // Dane alokowane na stosie
	} //  <- Tu nie ma wycieku pamięci bo dane są na stosie.
      // Po wyjsciu z zasięgu następuje ich zwolnienie.

    { 
		int * ptr = new int[12];
		delete [] ptr;
	} // <- Tu po wyjściu z zasiegu nie ma wycieku, nastąpiła
      // dealokacja przez delete.
      
    int * outside_ptr; // Wskaźnik "zewnętrzny dla zasięgu.
    {
		int * ptr = new int[12];
		// Tu może być jakaś logika która przepisze wskaźnik ptr do outside_ptr 
		// lub nie :-)
		// ... jeśli przepisze to warto zrobić tak....
		outside_ptr = ptr; // Przypisanie wskaźnika "żeby nie zgubić" danych
		ptr = nullptr;     // Przypisanie aby zadziałał delete
		// ... aby można było wykonać bezpiecznie delete na ptr
		// (bez względu na logikę).
		delete [] ptr;
	}
	// Co nie zmienia faktu że dane są we wskaźniku outisde_ptr i 
	// ... trzeba posprzątać :-)
	delete [] outside_ptr;
}
0
hyouka22 napisał(a):
  cout << &*arek << endl;

Operatory& i * znoszą się nawzajem (& pobiera adres wartości, a * "wyciąga" wartość spod adresu), więc wystarczy samo arek, bez żadnych dodatkowych znaczków. Oprócz tego jest OK :) Nie było new, więc delete nie jest potrzebny.

1 użytkowników online, w tym zalogowanych: 0, gości: 1