C Alokowanie pamięci na przykładzie - po co?

0

Witam, muszę ogarnąć temat struktur i dynamicznego alokowania pamięci. O ile samo w sobie nie jest to dla mnie truden to nie bardzo mogę sobie wyobrazić istoty dynamicznego alokowania pamięci. Jako najprostszy przykład wybrałem dodawanie elementu do listy jednokierunkowej. Przykład z dynamicznym alokowaniem pamięci:

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

struct Unit {
    int number;
    struct Unit *child;
};

void add(struct Unit *);

int main(void) {
    struct Unit main = {0};
    add(&main);
    printf("%d", main.child->number);
    return 0;
}

void add(struct Unit *parent) {
    struct Unit *start = parent;
    while (start->child) {
        start = start->child;
    }
    struct Unit *child = (struct Unit *) malloc(sizeof(struct Unit));
    child->number = 55;
    start->child = child;
}
 

Wszystko działa fajnie, a teraz funkcja "tradycyjna":

 
void add(struct Unit *parent) {
    struct Unit *start = parent;
    while (start->child) {
        start = start->child;
    }
    struct Unit child;
    child.number = 55;
    start->child = &child;
}

W obu przypadkach wynik taki sam. Po co (choćby w przypadku listy jednokierunkowej) używać dynamicznego alokowania pamięci? Co nam to w istocie daje, oprócz tego, że potem w mainie mogę sobie zwolnić tę pamięć? Chicłabym usłyszeć od kogoś praktyczną tego stronę. Dzięki!

0

W funkcji "tradycyjnej" obiekt child ma automatic storage duration. W skrócie oznacza to, że zostanie zniszczony przy wyjściu ze scope - tutaj przy wyjściu z funkcji add. Stąd start->child będzie wskazywał na ** zniszczony ** obiekt, tak zwany dangling pointer.

0

Faktycznie, o tym nie pomyślałem. W takim razie z ciekawości jak bardzo niebezpieczne używanie jest drugiej funkcji?

1

Bardzo. To zachowanie to tak zwane UB - undefined behaviour. Nie wiadomo co się stanie, a może się stać wszystko. Np zabije to Twojego kota. W praktyce będziesz odwoływał się do pamięci w której są śmieci (bo chociażby inny obiekt tam już będzie), w szczególności - przez rozszerzanie stosu - możliwa jest sytuacja w której ta pamięć nie będzie już Twoja, wtedy system operacyjny Cie kopnie w dupe.

0

W takim razie z ciekawości jak bardzo niebezpieczne używanie jest drugiej funkcji?

Druga funkcja jest po prostu błędna.

0

Rzeczywiście, teraz już wiem o do biega. W takim razie w mainie też warto zrobić "główną" jednostkę mallockiem czy tutaj już nie ma to znaczenia?

1

Powiedziałbym, że tak. Ale już lista powinna się zajmować zwalnianiem tej pamięci. Czyli w mainie robisz malloc natomiast w funkcji która czyści elementy listy robisz też free na "głownym elemencie".

1

Będzie dla Ciebie (i czytających kod) jak będziesz wszystko robił jednakowo, więc nie dziel list na te tworzone w main() i inne. Po prostu wszystkie alokuj przy pomocy malloc (jeśli jednak uzywasz C++, skieruj się w stronę new, a potem smart pointerów).

0

Obiecuję, że to będzie ostatnie (oby) pytanie :d

Mam prostą funkcję budującą listę, dlaczego pierwszy sposób działa doskonale:

 int main(void) {
    struct Unit *ptr = (struct Unit *) malloc(sizeof(struct Unit));
    struct Unit *list = build(ptr, 3);
    printf("%d", list->next->next->number);
    return 0;
}

struct Unit *build(struct Unit *list, int number) {
    if (number < 1) {
        return NULL;
    }
    struct Unit *start = list;
    start->number = 0;
    int i;
    for (i = 1; i < number; i++) {
        struct Unit *newUnit = (struct Unit *) malloc(sizeof(struct Unit));
        newUnit->number = i;
        start->next = newUnit;
        start = newUnit;
    }
    return list;
}

A taki sam, (tylko w funkcji zamiast przypisywać list do start od razu alokuję pamięć, nie korzystam z argumentu list) zaczyna "biegać" po pamięci i w efekcie zwraca błąd (kod błędu 139)?

int main(void) {
    struct Unit *ptr = (struct Unit *) malloc(sizeof(struct Unit));
    struct Unit *list = build(ptr, 3);
    printf("%d", list->next->next->number);
}

struct Unit *build(struct Unit *list, int number) {
    if (number < 1) {
        return NULL;
    }
    struct Unit *start = (struct Unit *) malloc(sizeof(struct Unit));;
    start->number = 0;
    int i;
    for (i = 1; i < number; i++) {
        struct Unit *newUnit = (struct Unit *) malloc(sizeof(struct Unit));
        newUnit->number = i;
        start->next = newUnit;
        start = newUnit;
    }
    return list;
} 
0

Dlatego, że w rozwiązaniu drugim zamiast zwracać start zwracasz parametr list. Stąd Twoje list z maina jest tym samym co ptr, a ptr nie ma żadnego next.

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