Iteracja po dynamicznie alokowanej tablicy

0

Witam, prosiłbym o naprowadzenie mnie na poprawny tok rozumowania. Mianowicie, mam poniższą funkcję:

int * apply_all(const int * const arr1, size_t size1, const int * const arr2, size_t size2){
	int* arr3 = new int[size1*size2];
	for (size_t i{ 0 }, k{ 0 }; i < size1; i++) {
		for (size_t j{ 0 }; j < size2; j++, k++) {
			arr3[k] = arr1[i] * arr2[j];
		}
	}
	return arr3;
}

I kombinuję teraz nad modyfikacją jej, a dokładniej chodzi mi sposób iterowania po arr3. Jak widać, używam zmiennej k, ale chciałbym zrobić to jakoś za pomocą notacji offsetowej i wpadłem na pomysł użycia:

*arr3++

Tylko oczywiście dostaje jakieś śmieciowe wyniki. W takim razie arr3 nie posiada początkowego adresu tablicy na stercie? To co znajduje się w arr3 po zainicjalizowaniu?

0

Tylko oczywiście dostaje jakieś śmieciowe wyniki.

Odpowiedź znajduje się tutaj:
Jaka operacja zostanie wykonana jako pierwsza w przypadku: *arr3++ ?
(*arr3)++ czy *(arr3++)

W takim razie arr3 nie posiada początkowego adresu tablicy na stercie

Na samym początku tak, ale jeśli potem inkrementujesz pointer (czyli go przesuwasz), to nie.

Edit:

Możesz zrobić pomocniczy pointer do iteracji po tablicy i koniecznie zwracać ten, który pointuje na początek.

2

Używając

*arr3++

po zakończeniu pętli, zwracasz wskaźnik pokazujący adres znajdujący się za adresem ostatniego elementu tablicy.
Czytanie wartości z poza zakresu powoduje wyświetlanie różne śmieci, a zapisywanie prowadzi do UB.

Jeżeli chcesz używać tej notacji to powinieneś zapisać adres tej tablicy.

int * apply_all(const int * const arr1, size_t size1, const int * const arr2, size_t size2){
    int* const arr3 = new int[size1*size2];
    int* iter = arr3;
    for (int i=0 ; i < size1; ++i) {
        for (int j=0 ; j < size2; ++j) {
            *iter++ = arr1[i] * arr2[j];
        }
    }
    return arr3;
}

Lepszym rozwiązanie jest tutaj użycie vectora, gdyż nie musisz się w tym przypadku zamartwić zwalnianiem i przydzielaniem pamięci oraz pilnowaniem zakresów tablic, co w dłuższej perspektywie uchroni cię od wielu nieprzyjemnych błędów.

vector<int> multiply( const vector<int>& arr1 , const vector<int>& arr2 )
{
    vector<int> result;

    for( const auto& element1 : arr1 )
    {
        for( const auto& element2 : arr2 )
        {
            result.push_back(element1*element2);
        }
    }

    return result;
}
5

A ja proponuje porzucić C na rzecz C++ i pisać kod bez cudów typu const int * const arr1 new int[size1*size2]; (twój kod używa C++, ale jest pisany tak jakby to było C).
Po to masz std::vector std::array std:span (c++20), żeby z nich korzystać.
Albo jeszcze lepiej zamknąć tą funkcjonalność w jakiejś klasie lub szablonie, żeby się z tego łatwo korzystało.

Polecam lekturę: https://dsp.krzaq.cc/post/176/ucze-sie-cxx-kiedy-uzywac-new-i-delete/

0
c7 napisał(a):

Tylko oczywiście dostaje jakieś śmieciowe wyniki.

Odpowiedź znajduje się tutaj:
Jaka operacja zostanie wykonana jako pierwsza w przypadku: *arr3++ ?
(*arr3)++ czy *(arr3++)

W takim razie arr3 nie posiada początkowego adresu tablicy na stercie

Na samym początku tak, ale jeśli potem inkrementujesz pointer (czyli go przesuwasz), to nie.

Edit:

Możesz zrobić pomocniczy pointer do iteracji po tablicy i koniecznie zwracać ten, który pointuje na początek.

A faktycznie, operatory mają taki sam priorytet ale odczytujemy od prawej do lewej, czyli najpierw przechodzę o 4 bajty w prawo a potem dopiero odczytuję wartość. Ale nawet jeśli pomijam pierwsze bajty, i wychodzę poza zakres to i tak powinienem mieć część wyników prawidłowych, a cały output to śmieciowe adresy. Nie rozumiem dlaczego tak się dzieje.

TomaszLiMoon napisał(a):

Używając

*arr3++

po zakończeniu pętli, zwracasz wskaźnik pokazujący adres znajdujący się za adresem ostatniego elementu tablicy.
Czytanie wartości z poza zakresu powoduje wyświetlanie różne śmieci, a zapisywanie prowadzi do UB.

Jeżeli chcesz używać tej notacji to powinieneś zapisać adres tej tablicy.

int * apply_all(const int * const arr1, size_t size1, const int * const arr2, size_t size2){
    int* const arr3 = new int[size1*size2];
    int* iter = arr3;
    for (int i=0 ; i < size1; ++i) {
        for (int j=0 ; j < size2; ++j) {
            *iter++ = arr1[i] * arr2[j];
        }
    }
    return arr3;
}

Lepszym rozwiązanie jest tutaj użycie vectora, gdyż nie musisz się w tym przypadku zamartwić zwalnianiem i przydzielaniem pamięci oraz pilnowaniem zakresów tablic, co w dłuższej perspektywie uchroni cię od wielu nieprzyjemnych błędów.

vector<int> multiply( const vector<int>& arr1 , const vector<int>& arr2 )
{
    vector<int> result;

    for( const auto& element1 : arr1 )
    {
        for( const auto& element2 : arr2 )
        {
            result.push_back(element1*element2);
        }
    }

    return result;
}

Wiem o możliwości użycia wektorów, ale chciałbym opanować i rozumieć dokładnie dynamiczne przydzielanie pamięci od fundamentów. Więc taka sama sprawa jak wyżej, nawet pomimo wychodzenia poza zakres i pomijana pierwszych 4 bajtów, każdy wynik to jakiś śmieciowy adres. Wydaje mi się, że środkowy przedział powinien mieć prawidłowe wartości? (chyba, że umyka mi gdzieś jakaś ważna zasada)

2
c7 napisał(a):

Edit:

Możesz zrobić pomocniczy pointer do iteracji po tablicy i koniecznie zwracać ten, który pointuje na początek.

Spróbuj zwrócić pointer na początek.

Wydaje mi się, że środkowy przedział powinien mieć prawidłowe wartości? (chyba, że umyka mi gdzieś jakaś ważna zasada)

Inkrementujesz swój wskaźnik, który zwracasz, czyli na końcu zwracasz wskaźnik za miejsce, które Cię interesuje.

0
c7 napisał(a):
c7 napisał(a):

Edit:

Możesz zrobić pomocniczy pointer do iteracji po tablicy i koniecznie zwracać ten, który pointuje na początek.

Spróbuj zwrócić pointer na początek.

Wydaje mi się, że środkowy przedział powinien mieć prawidłowe wartości? (chyba, że umyka mi gdzieś jakaś ważna zasada)

Inkrementujesz swój wskaźnik, który zwracasz, czyli na końcu zwracasz wskaźnik za miejsce, które Cię interesuje.

Faktycznie, przecież modyfikuje arr3 a potem zwracam ten sam adres, który powinien być nienaruszony i wskazywać na początek tablicy w pamięci...... Dzięki wszystkim za pomoc, dopiero teraz załapałem błąd.

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