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

Dynamiczna alokacja i dealokacja pamięci w C i C++

VPS Starter Arubacloud
+1 głos
3,526 wizyt
pytanie zadane 2 kwietnia 2017 w C i C++ przez Evelek Nałogowiec (28,960 p.)

Mam taki dylemat. Przedstawię najpierw kod w języku C:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int *wsk = (int*)malloc(10 * sizeof(int));
	for (int i = 0; i < 10; ++i)
		wsk[i] = 0;

	free(wsk[3]); //kompiluje się z ostrzeżeniem

	for (int i = 0; i < 10; ++i)
		printf("%d ", wsk[i]);
}

Najpierw alokuję dynamicznie pamięć dla wskaźnika - miejsce dla 10 wartości typu int. Następnie chciałbym zwolnić pamięć, która wskazuje na czwarty element tego wskaźnika za pomocą free(wsk[3]); Dostaje tutaj ostrzeżenie od kompilatora: "free": niezgodność wskaźnika na rzeczywisty parametr 1. Następnie wyświetlam elementy tej tablicy, wszystkie 10. Okazuje się, że zwalnianie pamięci nie zadziałało. Wszystkie elementy wskaźnika wyświetliły się poprawnie. Pytanie: czy jest więc sposób albo czy jest to w ogóle możliwe zwolnić pamięć wybranych przez siebie miejsc w pamięci na które wskazuje ten wskaźnik zostawiając przy tym pozostałe?

Kolejny kod to język C++:

#include <iostream>
using namespace std;

int main()
{
	int *wsk = new int[10];
	for (int i = 0; i < 10; ++i)
		wsk[i] = 0;

	delete[(wsk+9)] wsk; //kompiluje się z błędem

	//for (int i = 0; i < 10; ++i)
		//cout << wsk[i] << " ";
}

Ciekawostka z tego kodu, którą teraz zauważyłem: (Visual Studio 2017 RC) kompiluje się w całości ale pokazuje błąd: Wyrażenie musi mieć typ całkowitoliczbowy lub typ wyliczenia niewystępującego w zakresie. Mimo takiego błędu program się uruchamia. Pętla for z kodu jest w komentarzu - gdyby nie on, to oczywiście mamy program crashed i błąd odczytu pamięci. Pytanie więc to samo tylko w odniesieniu do C++: Czy jest sposób albo czy jest to w ogóle możliwe zwolnić pamięć wybranych przez siebie miejsc w pamięci na które wskazuje ten wskaźnik zostawiając przy tym pozostałe?

 

Jednym z pomysłów na jakie wpadłem było takie kombinowanie w C++, polegające na utworzeniu dynamicznie alokowanej tymczasowej tablicy, skopiowanie tam zawartości z tablicy głównej, a następnie dealokacja i alokacja tablicy głównej, ponowne skopiowanie zawartości do tablicy głównej i na koniec dealokacja tablicy tymczasowej.

int *tab = new int[10];
for (int i = 0; i < 10; ++i)
	tab[i] = 0;

int *tablica_tymczasowa = new int[9]; //utworzenie tymczasowej tablicy 
for (int i = 0; i < 9; ++i)
	tablica_tymczasowa[i] = tab[i]; //skopiowanie wartosci z glownej tablicy do tymczasowej bez ostatniego elementu

delete[] tab; //dealokacja pamieci glownej tablicy 
tab = new int[9]; //alokacja pamieci dla nowej pomniejszonej glownej tablicy 

for (int i = 0; i < 9; i++)
	tab[i] = tablica_tymczasowa[i]; //skopiowanie wartosci z tablicy tymczasowej do glownej 

delete[] tablica_tymczasowa; //dealokacja pamieci tablicy tymczasowej

Jednak zastanawiam się nad czymś wydajniejszym, bo przy większych ilościach elementów w tablicy takie alokacje, dealokacje, kopiowanie trochę czasu jednak zajmuje.

1 odpowiedź

+1 głos
odpowiedź 2 kwietnia 2017 przez criss Mędrzec (172,590 p.)
wybrane 2 kwietnia 2017 przez Evelek
 
Najlepsza
free(wsk[3]);

To jest UB. Z dokumentacji free:

If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior.

 Następnie wyświetlam elementy tej tablicy, wszystkie 10. Okazuje się, że zwalnianie pamięci nie zadziałało. 

Nawet zakładająć, że pamięć została poprawnie zwolniona, to: free (jak i delete czy delete[] z c++) jedynie zwalniają pamięć, ale jej nie modyfikują, więc dane zapisane w zwalnianej pamięci pozostaną tam dopóki system jej nie przedzieli innemu programowi i dane nie zostaną nadpisane.

Czy jest sposób albo czy jest to w ogóle możliwe zwolnić pamięć wybranych przez siebie miejsc w pamięci na które wskazuje ten wskaźnik zostawiając przy tym pozostałe?

Nope.

 Jednym z pomysłów na jakie wpadłem było(...)

To właśnie robi std::vector, żeby móc zmieniać swój rozmiar. Tnz. nie dokładnie to :D Znacznie ładniej, wydajniej i używając placement new, ale sam pomysł się zgadza (z resztą ciężko pomyśleć w inny sposób).

komentarz 2 kwietnia 2017 przez Evelek Nałogowiec (28,960 p.)

Placement new czyli miejscowa wersja new przypuszczam. Jeśli tak to super, bo wiem przynajmniej o co chodzi. wink Good to know Criss, że vector tak robi - mi tylko taki pomysł wpadł do głowy. Idąc dalej w tę stronę przypuszczam, że string ma identyczne zachowanie.

W drugą stronę, gdybyśmy chcieli dodać nowy element do tablicy zamiast go usuwać, to wpadło mi do głowy jeszcze, że w języku C mamy funkcję realloc(), więc nie trzeba dealokować i na nowo alokować pamięci tablicy. W języku C++ podobno nie ma takiej funkcji.

komentarz 2 kwietnia 2017 przez criss Mędrzec (172,590 p.)

Placement new to tworzenie dynamicznie obiektu w już zaalokowanej uprzednio pamięci. Wtedy jesteś zobowiązany "ręcznie" wywołać destruktor wspomnianego obiektu przed dealokacją pamięci.

Idąc dalej w tę stronę przypuszczam, że string ma identyczne zachowanie.

string nie wywołuje destruktorów przetrzymywanych elementów o ile dobrze kojarze. Poza tym jest bardzo podobny do vector-a.

Przykład placement new z cppreference:

char* ptr = new char[sizeof(T)]; // allocate memory
T* tptr = new(ptr) T;            // construct in allocated storage ("place")
tptr->~T();                      // destruct
delete[] ptr;                    // deallocate memory

w języku C mamy funkcję realloc(), więc nie trzeba dealokować i na nowo alokować pamięci tablicy.

To co zrobi realloc jest baaardzo mocno zależne od systemu, ale najczęściej to będzie po prostu dealokacja i ponowna alokacja. Z tego, co wiem, niektóre systemy w niektórych przypadkach są w stanie rozszerzyć sobie zaalokowany blok pamięci, ale to nie tak, że wystarczy, że użyjesz sobie realloc i to się stanie :P

1
komentarz 2 kwietnia 2017 przez mokrowski Mędrzec (155,460 p.)
@Chris, co do string'a to chyba woła destrukcję na całym kontenerze wewnętrznym jeśli alokacja na danej platformie wymagała zajęcia storage (najczęściej sterty). Dostępny jest wskaźnik data w ramach string'a który ma dostęp do 1 trzymanego znaku. Jak przeglądałem (z ciekawości) implementację w clang, nie da się za wiele z tym zrobić (tj. zmuszać string do trzymania czegoś innego niż znaki) bo asercja sprawdza czy typem szablonu string'a (std::basic_string) jest POD :-/ A.. i data to nie jest null-terminated string :-)
komentarz 2 kwietnia 2017 przez Evelek Nałogowiec (28,960 p.)

Mam taki programik przepisany z książki z użyciem placement new (według tłumaczenia w książce jest to miejscowa wersja new):

#include <iostream>
#include <new>
const int BUF = 512;
const int N = 5;
char buffer[BUF];

int main()
{
	using namespace std;

	double *pd1, *pd2;
	int i;
	cout << "Wywolanie new zwyklego i miejscowe:\n";
	pd1 = new double[N];
	pd2 = new (buffer) double[N];
	for (i = 0; i < N; i++)
		pd2[i] = pd1[i] = 1000 + 20.0 * i;
	cout << "Adres pamieci:\n" << " sterta: " << pd1 << " pamiec statyczna: " << (void*)buffer << endl;
	cout << "Zawartosc pamieci:\n";
	for (i = 0; i < N; i++) {
		cout << pd1[i] << " pod adresem " << &pd1[i] << "; ";
		cout << pd2[i] << " pod adresem " << &pd2[i] << endl;
	}

	cout << "\nDrugie wywolanie zwyklego i miejscowego new:\n";
	double *pd3, *pd4;
	pd3 = new double[N];
	pd4 = new (buffer) double[N];
	for (i = 0; i < N; i++)
		pd4[i] = pd3[i] = 1000 + 40.0 * i;
	cout << "Adres pamieci:\n" << " sterta: " << pd1 << " pamiec statyczna: " << (void*)buffer << endl;
	cout << "Zawartosc pamieci:\n";
	for (i = 0; i < N; i++) {
		cout << pd3[i] << " pod adresem " << &pd3[i] << "; ";
		cout << pd4[i] << " pod adresem " << &pd4[i] << endl;
	}

	cout << "\nTrzecie wywolanie zwyklego i miejscowego new:\n";
	delete[] pd1;
	pd1 = new double[N];
	pd2 = new (buffer + N * sizeof(double)) double[N];
	for (i = 0; i < N; i++)
		pd2[i] = pd1[i] = 1000 + 60.0 * i;
	cout << "Adres pamieci:\n" << " sterta: " << pd1 << " pamiec statyczna: " << (void*)buffer << endl;
	cout << "Zawartosc pamieci:\n";
	for (i = 0; i < N; i++) {
		cout << pd1[i] << " pod adresem " << &pd1[i] << "; ";
		cout << pd2[i] << " pod adresem " << &pd2[i] << endl;
	}

	delete[] pd1;
	delete[] pd3;
	cin.get();
}

Mam jeszcze jeden podobny z użyciem obiektów klasy (gdzie trzeba zwrócić uwagę na kilka drobnych szczegółów), ale pominę to, bo wiemy o czym rozmawiamy.

Podobne pytania

0 głosów
2 odpowiedzi 778 wizyt
0 głosów
2 odpowiedzi 561 wizyt
pytanie zadane 8 kwietnia 2017 w C i C++ przez Mikusbombro Użytkownik (990 p.)
0 głosów
1 odpowiedź 414 wizyt
pytanie zadane 7 maja 2020 w C i C++ przez Hubertius Bywalec (2,970 p.)

92,452 zapytań

141,262 odpowiedzi

319,077 komentarzy

61,854 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!

...