Załóżmy, że chcę zdefiniować strukturę reprezentującą długość wektora i jego wartości jako:
struct Vector{
double* x;
int n;
};
Teraz przypuśćmy, że chcę zdefiniować wektor y i przydzielić mu pamięć.
struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));
Moje wyszukiwania w Internecie pokazują, że powinienem przydzielić pamięć dla x osobno.
y->x = (double*)malloc(10*sizeof(double));
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ę.
c
memory-management
Pouya
źródło
źródło
malloc()
w C. Nigdy nie zrozumiem, dlaczego wszyscy czują taką potrzebę. :(void *
nie konwertuje automatycznie na inne wskaźniki, a rzutowanie jest potrzebne (lub po prostu nie używajmalloc()
w C ++, oczywiście).Odpowiedzi:
Nie, nie przydzielasz pamięci
y->x
dwukrotnie.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:
1 2 +-----+ +------+ y------>| x------>| *x | | n | +------+ +-----+
Potrzebujesz więc dwóch alokacji (
1
i2
) do przechowywania wszystkiego.Dodatkowo twój typ powinien być,
struct Vector *y
ponieważ jest wskaźnikiem i nigdy nie powinieneś rzutować wartości zwracanej zmalloc
C, ponieważ może to ukryć pewne problemy, których nie chcesz ukryć - C jest w pełni zdolny do niejawnej konwersjivoid*
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:
struct Vector { double *data; // no place for x and n in readable code :-) size_t size; }; struct Vector *newVector (size_t sz) { // Try to allocate vector structure. struct Vector *retVal = malloc (sizeof (struct Vector)); if (retVal == NULL) return NULL; // Try to allocate vector data, free structure if fail. retVal->data = malloc (sz * sizeof (double)); if (retVal->data == NULL) { free (retVal); return NULL; } // Set size and return. retVal->size = sz; return retVal; } void delVector (struct Vector *vector) { // Can safely assume vector is NULL or fully built. if (vector != NULL) { free (vector->data); free (vector); } }
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ść).
źródło
Za pierwszym razem, można przydzielić pamięć
Vector
, co oznacza zmiennex
,n
.Jednak
x
nie wskazuje jeszcze niczego przydatnego .Dlatego potrzebny jest również drugi przydział .
źródło
Kilka punktów
struct Vector y = (struct Vector*)malloc(sizeof(struct Vector));
jest źlepowinno być,
struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));
ponieważy
zawiera wskaźnik dostruct 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.źródło
W rzeczywistości można to zrobić w jednym malloc, przydzielając wektor i tablicę w tym samym czasie. Na przykład:
struct Vector y = (struct Vector*)malloc(sizeof(struct Vector) + 10*sizeof(double)); y->x = (double*)((char*)y + sizeof(struct Vector)); y->n = 10;
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”.
źródło
W zasadzie już robisz to poprawnie. Na to, co chcesz, potrzebujesz dwóch
malloc()
sekund.Kilka uwag:
struct Vector y = (struct Vector*)malloc(sizeof(struct Vector)); y->x = (double*)malloc(10*sizeof(double));
Powinien być
struct Vector *y = malloc(sizeof *y); /* Note the pointer */ y->x = calloc(10, sizeof *y->x);
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 *y
zamiast tegosizeof(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ą:struct Vector{ int n; double x[]; }; struct Vector *y = malloc(sizeof *y + 10 * sizeof(double));
źródło
if(NULL == foo)
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 ...sizeof *y
pomaga walczyć z błędami, takimi jak pisanie w odpowiednimsizeof(Vector)
momenciesizeof(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 *y
we własnym kodzie, ale jest to prawie tak samo paranoiczne, jak pisanie,if(NULL == foo)
aby zapobiec literówkom w==
.y
jest wskaźnikiem dostruct Vector
sosizeof *y
znaczy "rozmiar na co wskazuje y", więcsizeof struct Vector
.Kiedy przydzielasz pamięć
struct Vector
, po prostu przydziel pamięć dla wskaźnikax
, 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óregoy.x
będzie się odwoływać.źródło
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.
źródło
Kiedy
malloc(sizeof(struct_name))
automatycznie przydziela pamięć dla pełnego rozmiaru struktury, nie musisz zmieniać każdego elementu wewnątrz.Użyj
-fsanitize=address
flagi, aby sprawdzić, jak wykorzystałeś pamięć programu.źródło