[C++] Używanie memset'a do zerowania tablicy dwuwymiarowej


(Namekxxx) #1

Mam taki fragment kodu:

int szerokosc, wysokosc;

char znak;

cin>>szerokosc>>wysokosc;

piksel = new int*[wysokosc];

memset(piksel, 0, wysokosc);

for (int i=0; i
{

    piksel[i] = new int[szerokosc];

    memset(piksel[i], 0, szerokosc);

    for (int j=0; j
    {

        cin>>znak;

        if(znak=='+')

            piksel[i][j] = wysokosc*szerokosc;

        else if(znak=='<')

            piksel[i][j] = -1;

    }

}

Z memseta korzystałem wiele razy, i nigdy nie miałem z nim problemu, aż do teraz. Potrzebuję wyzerować (przed wypełnieniem) tablicę dwuwymiarową. Spoko, niby zeruje, ale sprawdziłem (wypisując wszystkie elementy), że dla jednego przypadku danych wejściowych, nie zeruje mi drugiej kolumny, czyli piksel_[1]. Próbowałem chyba 8 różnych danych wejściowych (powiedzmy map) i działa ok, problem jej tylko z tymi danymi:_

5 5

=====

=<+<=

=++<=

=<<<=

=====

Dla innych danych, np tych:

10 6

==========

=+<<<+++==

=+<+++====

=+<<<+++==

====++++==

==========

wszystko się zeruje, przed wypełnieniem.

Gdzie popełniam błąd?


(Razi) #2

W tym zadaniu memset jest niepotrzebny. Iterujesz po każdej komórce, masz przypisanie do tej komórki dla jednego warunku, dla drugiego i możesz śmiało za tym dać else i tam przypisać wartość domyślną.

U mnie jakoś normalnie zeruje, kompilator GCC 4.6.1. Może akurat nie mam aż tak zapchanej pamięci żeby tam były jakieś śmieci.

Swoją drogą zerowanie dla tab_, gdy tablica jest dwuwymiarowa też jest trochę bez sensu._


(Namekxxx) #3

Wiem, że w tym zadaniu można pominąć memseta, a po wczytaniu znaku, po sprawdzeniu dwóch warunków, wstawić trzeci warunek else piksel__[j]=0; ale chciałbym skorzystać akurat z memseta.

Jeśli chodzi o ostatnie zdanie, to też się zgadzam, ale próbowałem wszystkich możliwości użycia memseta, we wszystkich możliwych miejscach i widocznie przez pomyłkę wkleiłem kod z dwukrotnym zerowaniem.

Mam GCC w wersji 4.4.1, ale już nie będę kombinował z instalacją nowego kompilatora, bo nie mam zbytnio czasu ;/. Chyba jednak sobie odpuszczę tego memseta.


(Razi) #4

Dla wersji 4.4.6 też działa poprawnie, starszych nie mam w repozytorium niestety, ale jeśli to GCC w wersji 4.x nie sądzę by to była wina kompilatora. Z jakimi flagami kompilujesz?


(Namekxxx) #5

Robię to w CodeBlocksie, nie mam zaznaczonych żadnych flag w ustawieniach.


(Fiołek) #6

Tak, szukajcie błędu w bibliotece standardowej kompilatora... :wink:

Z memseta może i korzystałeś wiele razy, ale NIE WIESZ jak z niego korzystać.

http://www.cplusplus.com/reference/clib ... ng/memset/ - przypatrz się opisowi parametrów, szczególnie trzeciego, i porównaj to z tym, co podajesz, tylko naprawdę się przypatrz i naprawdę porównaj, a nie "tak mi się wydaje".


(Namekxxx) #7

Dawałem wcześniej sizeof(piksel), jeśli o to Ci chodzi, ale było dokładnie to samo. Odpuszczam.


(Fiołek) #8

Tak, chodzi mi o rozmiar danych. Masz wysokość * szerokość elementów, co daje wysokość * szerokość * sizeof(int)(zazwyczaj 4) bajtów. Samo sizeof(piksels) zwraca sizeof(int**) co na systemie 32-bitowym(i char == 8B, ale chyba żaden kompilator/normalna platforma domyślnie nie robi tego inaczej) daje 4, a 64-bitowym - 8. Tak więc pomnóż i szerokość, i wysokość przez odpowiedni typ i będzie działać, a jak nie będzie to zwaliłeś coś w innym miejscu(i na przyszłość - "spłaszczaj" tablice wielowymiarowe w jedno, łatwiej się zarządza).


(kostek135) #9

Ja nie ogarniam jednego czemu ty to robisz tak po mongolsku. Zerujesz tablice wskaźników (czyt. drugi wymiar), następnie tablice właściwą z danymi zerujesz po kawałku? Zrób to tak

int main() {

    int dim1 = 10;

    int dim2 = 10;

    int** macierz2D = new int*[dim1];

    int* dumm = new int[dim1 * dim2];


    for(int i = 0; i < dim1; i++)

        *(macierz2D + i) = dumm + i * dim2;


    memset(dumm, 0, dim1 * dim2 * sizeof(int));


    macierz2D[3][3] = 1;


    for(int i = 0; i < dim1; i++) {

        for(int j = 0; j < dim2; j++)

            printf("%d ", macierz2D[i][j]);

        printf("\n");

    }

    return 0;

}

Dzięki temu mamy dwie alokacje, a z twojego czyszczenia po kawałku wnioskuje, że na 100% pofragmentowałeś pamięć przez new w pętli. New w pętli robi się tylko w skarajnych przypadkach, jak dynamicznie rozszerzalna lista łancuchowa, a jak wiesz ile maksymalnie uzyjesz pamieci to korzystaj z pól pamięci do takich rzeczy. Ponadto, używamy raz memset-a, robienie go w pętli mija się z celem, jego nalezy uzywac do jak najwiekszych kawałków pamięci inaczej nie zuwazysz róznicy miedzy nim ,a petlą zerująca. Uruchom kod, przeanalizuj.Tworzymy dwie tablice wskazników na tablice intów i tablice intów, co dziesiec intów z drugiej tablicy wskazujemy kolejnym wskażnikiem, co onacza tyle ze odwołanie przez ten wskaznik da nam skok o dziesiec (czyli o nasz wiersz).

EDIT

Dodatkowa korzysc takiej alokacji, to dwa delete. Nic nie zgubisz, bo dwa wskaźniki masz (musiał byś cos pod nie przypisać by je zgubic), a w twoim sposobie jak zwolnisz wpierw tablice wskaźników na int to powodzenia, może jeszcze nikt nie nadpisał tej pamięci i sie odwołasz w dobre miejsce.


(Fiołek) #10

@up: nie chce nic mówić, ale to Twoje jest po mongolsku(po kiego grzyba "macierz2D"?) :stuck_out_tongue:

* tab = new int[w*h];memset(tab, 0, sizeof(int)*w*h);tab[3 * w + 3] = 1;int y = 0; y  h; ++y)

(kostek135) #11

Zeby ci co nie rozumieja wskaźników, mogli sie odwoływać do komórki przez [][].


(Fiołek) #12

Tym, co nie rozumieją jak działają tablice(bo tu nawet nie o wskaźniki chodzi), można jedynie współczuć. Nie ma sensu sobie życia utrudniać przez jakieś pseudo-pomocnicze rzeczy.


(kostek135) #13

Niewątpliwie, masz rację. Ale dałem mu odpowiedź na pytanie w możliwe jak najbardziej user-friendly sposob, działa i moze sobie pisać co tam chce używając podwójnej dereferencji. A na pewno jest to lepsze od robienia new w petli, co wnioskuje z zamazywania pamieci kawałkami.


(Namekxxx) #14

@kostek135, pomogło, dzięki bardzo :).