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ę.


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ść).


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ł .


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.


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”.


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ą:


„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ć.


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.


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.


Ź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,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.