malloc dla struktury i wskaźnika w C

84

Załóżmy, że chcę zdefiniować strukturę reprezentującą długość wektora i jego wartości jako:

Teraz przypuśćmy, że chcę zdefiniować wektor y i przydzielić mu pamięć.

Moje wyszukiwania w Internecie pokazują, że powinienem przydzielić pamięć dla x osobno.

Ale wydaje się, że przydzielam pamięć dla y-> x dwa razy, jeden podczas przydzielania pamięci dla y, a drugi podczas przydzielania pamięci dla y-> x, i wydaje się, że jest to marnowanie pamięci. Byłoby bardzo wdzięczne, gdybyśmy wiedzieli, co naprawdę robi kompilator i jaki byłby właściwy sposób inicjalizacji zarówno y, jak i y-> x.

Z góry dziękuję.

Pouya
źródło
5
Jak wyraźnie wskazał paxdiablo, proszę nie rzucać wartości zwracanej malloc()w C. Nigdy nie zrozumiem, dlaczego wszyscy czują taką potrzebę. :(
odpręż się
14
@unwind, może to starzy programiści C ++ przechodzący na C :-)
paxdiablo
@unwind Kiedy używam kompilatora nvcc firmy Nvidia w kodzie C, jeśli nie rzutuję wyniku malloc, generuje błąd.
Nubcake,
@Nubcake Zgodnie z tym linkiem może to być spowodowane tym, że nvcc uruchamia bazowy kompilator w trybie C ++, ponieważ ich interfejs CUDA to C ++. W C nie dostaniesz za to błędów. W C ++ void *nie konwertuje automatycznie na inne wskaźniki, a rzutowanie jest potrzebne (lub po prostu nie używaj malloc()w C ++, oczywiście).
odpoczynek
@unwind Tak, później się o tym dowiedziałem :) Chciałem tylko opisać sytuację, w której jeśli nie rzucisz wyniku, to wyrzuci błąd.
Nubcake

Odpowiedzi:

156

Nie, nie przydzielasz pamięci y->xdwukrotnie.

Zamiast tego przydzielasz pamięć dla struktury (która zawiera wskaźnik) oraz coś, na co ten wskaźnik ma wskazywać.

Pomyśl o tym w ten sposób:

Potrzebujesz więc dwóch alokacji ( 1i 2) do przechowywania wszystkiego.

Dodatkowo twój typ powinien być, struct Vector *yponieważ jest wskaźnikiem i nigdy nie powinieneś rzutować wartości zwracanej z mallocC, ponieważ może to ukryć pewne problemy, których nie chcesz ukryć - C jest w pełni zdolny do niejawnej konwersji void*zwracanej wartości na dowolny inny wskaźnik.

I oczywiście prawdopodobnie chcesz hermetyzować tworzenie tych wektorów, aby ułatwić zarządzanie nimi, na przykład:

Hermetyzując w ten sposób kreację, zapewniasz, że wektory są w pełni zbudowane lub w ogóle nie są zbudowane - nie ma szans, że zostaną zbudowane w połowie. Pozwala także w przyszłości całkowicie zmienić podstawowe struktury danych bez wpływu na klientów (na przykład, jeśli chcesz, aby były rzadkimi tablicami, aby wymienić miejsce na szybkość).

paxdiablo
źródło
28
Tremble at my ASCII Art Skillage :-)
paxdiablo
Dziękuję bardzo. Naprawdę pomocny.
Pouya,
1
In if (retval == NULL), retval powinno być retVal
Legion Daeth
5

Za pierwszym razem, można przydzielić pamięć Vector, co oznacza zmienne x, n.

Jednak x nie wskazuje jeszcze niczego przydatnego .

Dlatego potrzebny jest również drugi przydział .

Karthik T
źródło
3

Kilka punktów

struct Vector y = (struct Vector*)malloc(sizeof(struct Vector)); jest źle

powinno być, struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));ponieważ yzawiera wskaźnik do struct Vector.

Pierwszy malloc()przydziela tylko tyle pamięci, aby pomieścić strukturę Vector (czyli wskaźnik do double + int)

2. malloc()faktycznie przydziel pamięć, aby pomieścić 10 podwójnych.

rajneesh
źródło
2

W rzeczywistości można to zrobić w jednym malloc, przydzielając wektor i tablicę w tym samym czasie. Na przykład:

To przydziela Vector 'y', a następnie wskazuje y-> x na dodatkowe przydzielone dane bezpośrednio po strukturze Vector (ale w tym samym bloku pamięci).

Jeśli wymagana jest zmiana rozmiaru wektora, należy to zrobić z dwoma alokacjami zgodnie z zaleceniami. Wewnętrzna tablica y-> x mogłaby być wtedy zmieniana, zachowując nienaruszoną strukturę wektora „y”.

PQuinn
źródło
Dlaczego napisałeś cast y specjalnie do zwęglać? Dlaczego nie (double *) y + sizeof (struct Vector)?
Vishnu Prasath
sizeof zwraca rozmiar struktury w bajtach, a operator arytmetyczny wskaźnika „+” doda do wskaźnika „y” wielokrotności rozmiaru sizeof ( y). Gdybyśmy zrobili tak, jak powyżej, y zostałoby zwiększone o sizeof (double) * sizeof (struct), czyli za dużo. Rzutowanie y na char pozwala nam zwiększyć y o sizeof (char) * sizeof (struct) = 1 * sizeof (struct)
PQuinn
2

W zasadzie już robisz to poprawnie. Na to, co chcesz, potrzebujesz dwóch malloc()sekund.

Kilka uwag:

Powinien być

W pierwszym wierszu przydzielasz pamięć dla obiektu Vector. malloc()zwraca wskaźnik do przydzielonej pamięci, więc y musi być wskaźnikiem Vector. W drugiej linii przydzielasz pamięć dla tablicy 10 podwójnych.

W C nie potrzebujesz jawnych rzutów, a pisanie sizeof *yzamiast tego sizeof(struct Vector)jest lepsze ze względu na bezpieczeństwo typów, a poza tym oszczędza na pisaniu.

Możesz zmienić układ swojej struktury i zrobić jedną malloc()taką:

Wernsey
źródło
„Oszczędność na wpisywaniu” nigdy nie jest ważnym argumentem przy decyzjach programistycznych. Prawdziwym powodem, dla którego wziąłbyś * y, jest ze względów bezpieczeństwa, zapewnienie, że przydzielisz tyle miejsca, ile potrzeba dla odpowiedniej zmiennej.
Lundin,
@Lundin Zaktualizowałem moją odpowiedź, ale dla mnie argument "bezpieczeństwa typu" jest prawie w tej samej lidze co pisanieif(NULL == foo)
Wernsey,
Jesteś tym, którego możesz użyć sizeof *y. Jeśli robisz to tylko ze względu na wpisanie 5 liter mniej (sizeof * y versus sizeof (Vector)) bez żadnego innego argumentu, to musi to być spowodowane tym, że wpisanie 5 liter na klawiaturze jest dla Ciebie główną przeszkodą. A jeśli tak, to może rozważ inny wybór kariery, ponieważ programowanie wiąże się z mnóstwem pisania na klawiaturze ...
Lundin,
@Lundin Writing sizeof *ypomaga walczyć z błędami, takimi jak pisanie w odpowiednim sizeof(Vector)momencie sizeof(Matrix). Jak często popełniasz takie błędy? Jak szybko je znajdujesz i naprawiasz? Zgadzam się, że zwiększa to bezpieczeństwo typów i piszę sizeof *ywe własnym kodzie, ale jest to prawie tak samo paranoiczne, jak pisanie, if(NULL == foo)aby zapobiec literówkom w ==.
Wernsey,
1
@ShmuelKamensky Są funkcjonalnie równoważne. yjest wskaźnikiem do struct Vectorso sizeof *yznaczy "rozmiar na co wskazuje y", więc sizeof struct Vector.
Wernsey
1

Kiedy przydzielasz pamięć struct Vector, po prostu przydziel pamięć dla wskaźnika x, czyli miejsca, w którym zostanie umieszczona jego wartość zawierająca adres. W ten sposób nie przydziela się pamięci dla bloku, do którego y.xbędzie się odwoływać.

Andremoniy
źródło
1

Najpierw malloc przydziela pamięć dla struct, w tym pamięć dla x (wskaźnik do double). Drugi malloc przydziela pamięć dla podwójnej wartości, na którą x punktów.

oleg_g
źródło
0

Kiedy malloc(sizeof(struct_name))automatycznie przydziela pamięć dla pełnego rozmiaru struktury, nie musisz zmieniać każdego elementu wewnątrz.

Użyj -fsanitize=addressflagi, aby sprawdzić, jak wykorzystałeś pamięć programu.

Stały Asg
źródło
Źle. x to tylko wskaźnik, musisz przydzielić pamięć dla wartości x wskazuje. Przeczytaj inną odpowiedź, aby uzyskać więcej informacji. (np .: stackoverflow.com/a/14768280/5441253 )
Maxime Ashurov,