Czy malloc () przydziela ciągły blok pamięci?


Mam kawałek kodu napisany przez bardzo starego programistę :-). brzmi to mniej więcej tak
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def; ts_request_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

programista pracuje głównie nad koncepcją przepełnienia bufora. Wiem, że kod wygląda na wątpliwy. Więc moje pytania to:
  • Czy malloc zawsze przydziela ciągły blok pamięci ponieważ w tym kodzie, jeśli bloki nie są ciągłe, kod będzie długo nie działać
  • Wykonując
    free (request_buffer)
    , zwolni wszystkie bajty przydzielone przez malloc, to znaczy
    sizeof (ts_request_def) + (2 * 1024 * 1024)
    , lub tylko bajty rozmiaru struktury
    sizeof (ts_request_def)
  • Czy widzisz jakieś oczywiste problemy z tym podejściem, muszę omówić to z moim szefem i chciałbym wskazać wszelkie luki w tym podejściu

Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Aby odpowiedzieć na numerowane pozycje.
  • Tak.
  • Wszystkie bajty. Malloc/free nie znają i nie dbają o rodzaj przedmiotu, tylko o jego wielkość.
  • Ściśle mówiąc, jest to niezdefiniowane zachowanie, ale powszechna sztuczka obsługiwana przez wiele implementacji. Zobacz poniżej inne alternatywy.

Pozwala na to najnowszy standard C, ISO/IEC 9899: 1999 (nieformalnie C99)

elastyczne elementy tablicy
http://www.comeaucomputing.com ... rrays
.
Przykładem może być:
int main(void)
{
struct { size_t x; char a[]; } *p;
p = malloc(sizeof *p + 100);
if (p)
{
/* You can now access up to p->a[99] safely */
}
}

Ta ustandaryzowana funkcja pozwoliła Ci uniknąć używania popularnego, ale niestandardowego rozszerzenia implementacji, które opisałeś w swoim pytaniu. Ściśle mówiąc, używanie nieelastycznego elementu tablicy i dostęp poza nim jest niezdefiniowanym zachowaniem, ale wiele implementacji dokumentuje to i zachęca do tego.
Oprócz,

gcc
http://gcc.gnu.org/
przyznaje

tablice o zerowej długości
http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
jako rozszerzenie. Tablice o zerowej długości nie są dozwolone w standardzie C, ale gcc wprowadził tę funkcję, zanim C99 dał nam elastyczne składowe tablicy.
W odpowiedzi na komentarz wyjaśnię, dlaczego poniższy fragment kodu jest technicznie niezdefiniowanym zachowaniem. Numery punktów, które cytuję, odnoszą się do C99 (ISO/IEC 9899: 1999)
struct {
char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;

Po pierwsze, 6.5.2.1 # 2 pokazuje, że a [i] jest identyczne z (* ((a) + (i))), więc x- & > arr [23] jest równoważne z (* ((x - & gt ; arr) + (23))). Teraz 6.5.6 # 8 (O dodawaniu wskaźników i liczb całkowitych) mówi:

„Jeśli zarówno operand wskaźnika, jak i wynik wskazują na elementy tego samego obiektu tablicy lub jeden po ostatnim elemencie obiektu tablicy, obliczenia nie powinny przepełniać; w przeciwnym razie

zachowanie jest nieokreślone

."

Z tego powodu, ponieważ x- & > arr [23] nie znajduje się wewnątrz tablicy, zachowanie jest niezdefiniowane. Nadal możesz myśleć, że to jest w porządku, ponieważ malloc () sugeruje, że tablica została teraz rozszerzona, ale nie jest to do końca prawdą. Dodatek informacyjny J.2 (zawierający przykłady nieokreślonych zachowań) zawiera dalsze wyjaśnienia na przykładzie:

Indeks tablicy jest poza zakresem, mimo że obiekt wydaje się być dostępny za pomocą
podany indeks (jak w wyrażeniu lwartość a [1] [7] z podaną deklaracją int
a[4][5]) (6.5.6).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

3 - Jest to dość powszechna sztuczka w C do przydzielania tablicy dynamicznej na końcu struktury. Alternatywą byłoby umieszczenie wskaźnika w strukturze, a następnie oddzielne przydzielenie tablicy, nie zapominając również o jej zwolnieniu. Fakt, że rozmiar jest ustalony na 2 MB, wydaje się nieco niezwykły.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

1) Tak, lub malloc zawiedzie, jeśli nie będzie wystarczająco dużego, ciągłego bloku. (Błąd z malloc zwróci wskaźnik NULL)
2) Tak, będzie. Alokacja pamięci wewnętrznej będzie śledzić ilość pamięci przydzielonej za pomocą tej wartości wskaźnika i całkowicie ją zwolnić.
3) jest to trochę hack językowy i trochę wątpliwe, czy jest używany. Nadal jest podatny na przepełnienia bufora, po prostu atakujący może zająć trochę więcej czasu, aby znaleźć ładunek, który go wyzwala. Koszt „ochrony” jest również dość wysoki (czy naprawdę potrzebujesz> 2 MB na bufor żądań?). Jest też bardzo brzydki, chociaż Twój szef może nie doceniać tego argumentu :)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

To jest standardowa sztuczka C i nie jest bardziej niebezpieczna niż jakikolwiek inny bufor.
Jeśli próbujesz pokazać swojemu szefowi, że jesteś mądrzejszy niż „bardzo stary programista”, ten kod nie jest dla Ciebie. Stara szkoła niekoniecznie jest zła. Wygląda na to, że gość ze starej szkoły wie wystarczająco dużo o zarządzaniu pamięcią;)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie sądzę, aby istniejące odpowiedzi docierały do ​​sedna tego pytania. Mówisz, że programista ze starej szkoły robi coś takiego;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

Myślę, że jest mało prawdopodobne, aby zrobił to dokładnie, ponieważ jeśli tego chciał, mógłby to zrobić za pomocą uproszczonego równoważnego kodu, który nie wymaga żadnych sztuczek;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[2*1024*1024 + 1];
} ts_request_def;ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def));

Założę się, że faktycznie robi coś takiego;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];// effectively package[x]
} ts_request_def;ts_request_buffer_def* request_buffer =
malloc( sizeof(ts_request_def) + x );

To, co chce osiągnąć, to przydzielenie żądania ze zmiennym rozmiarem pakietu X. Oczywiście deklarowanie rozmiaru tablicy zmiennej jest nielegalne, więc omija to sztuczką. Wygląda na to, że wie, co mi robi, sztuczka polega na zbliżeniu się do szanowanego i praktycznego końca skali oszustwa C.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli chodzi o # 3, bez dodatkowego kodu, trudno odpowiedzieć. Nie widzę w tym nic złego, chyba że zdarza się to często. Chodzi mi o to, że nie chcesz przez cały czas przydzielać 2 MB pamięci. Nie chcesz też robić tego niepotrzebnie, na przykład jeśli kiedykolwiek używasz tylko 2k.
Fakt, że nie podoba ci się to z jakiegokolwiek powodu, nie wystarczy, aby się z nim spierać lub w pełni uzasadnić jego przepisanie. Przyjrzałbym się bliżej użyciu, spróbował zrozumieć, o czym myślał oryginalny programista, przyjrzałbym się przepełnieniu bufora (jak wskazał workmad3) w kodzie używającym tej pamięci.
Istnieje wiele typowych błędów, które możesz znaleźć. Na przykład, czy kod sprawdza, czy funkcja malloc () się powiodła?
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Exploit (pytanie 3) naprawdę zależy od interfejsu tego frameworka. W kontekście taka alokacja może mieć sens, a bez dodatkowych informacji nie można powiedzieć, czy jest to bezpieczne, czy nie.

Ale jeśli masz na myśli więcej problemów z alokacją pamięci niż struct, to wcale nie jest zły projekt C (nie powiedziałbym nawet, że to oldschoolowe TO ...;))

Jeszcze ostatnia uwaga - chodzi o to, że znak [1] polega na tym, że kończący NULL będzie zawsze w zadeklarowanej strukturze, co oznacza, że ​​w buforze może znajdować się 2 * 1024 * 1024 znaki i nie trzeba brać pod uwagę Wartości NULL z „+1”. To może brzmieć jak mały wyczyn, ale chciałem tylko to wskazać.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Często widziałem i korzystałem z tego wzoru.
Ma tę zaletę, że upraszcza zarządzanie pamięcią, a tym samym zapobiega ryzyku wycieków pamięci. Wystarczy uwolnić blok przewijania malloc. Z dodatkowym buforem potrzebujesz dwóch darmowych. Należy jednak zdefiniować i użyć funkcji destruktora, aby hermetyzować tę operację, aby zawsze można było zmienić jej zachowanie, na przykład przełączyć się na bufor buforowy lub dodać dodatkowe operacje, które mają być wykonane po usunięciu struktury.
Dostęp do elementów tablicy jest również nieco wydajniejszy, ale w przypadku nowoczesnych komputerów ma to coraz mniejsze znaczenie.
Kod będzie również działał poprawnie, jeśli wyrównanie pamięci zmieni się w strukturze z różnymi kompilatorami, ponieważ zdarza się to dość często.
Jedynym potencjalnym problemem, jaki widzę, jest to, że kompilator zmienia kolejność, w jakiej przechowywane są zmienne składowe, ponieważ ta sztuczka wymaga, aby pole pakietu pozostało ostatnie w sklepie. Nie wiem, czy standard C zabrania permutacji.
Należy również zauważyć, że przydzielony bufor prawdopodobnie będzie większy niż wymagany o co najmniej jeden bajt, z dodatkowymi bajtami wypełniającymi, jeśli takie istnieją.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tak. malloc zwraca tylko jeden wskaźnik - jak może powiedzieć żądającemu, że przydzielił wiele nieciągłych bloków, aby spełnić żądanie?
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Chciałbym dodać, że nie jest to zbyt częste, ale równie dobrze mogę nazwać to standardową praktyką, ponieważ API systemu Windows jest pełne takich zastosowań.
Na przykład sprawdź bardzo popularną strukturę nagłówka BITMAP.
http://msdn.microsoft.com/en-u ... .aspx
http://msdn.microsoft.com/en-u ... .aspx
Ostatnią ćwiartką RBG jest tablica o rozmiarze 1, która zależy od tej konkretnej techniki.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

W odpowiedzi na twoje trzecie pytanie.
free
zawsze zwalnia całą przydzieloną pamięć za jednym razem.
int* i = (int*) malloc(1024*2);free(i+1024);// gives error because the pointer 'i' is offsetfree(i);// releases all the 2KB memory
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Ta powszechna sztuczka C jest również wyjaśniona w

Zobacz to pytanie StackOverflow (czy ktoś może wyjaśnić tę definicję struktury dirent w solaris?)
https://coderoad.ru/563045/
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Odpowiedz na pytania 1 i 2 Tak
Co do hańby (tj. Pytanie 3), co programista próbuje zrobić z tą przydzieloną pamięcią?
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

tutaj musisz zrozumieć, że
malloc
nie widzi obliczeń wykonywanych w tym przypadku.
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

To jest to samo co
int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
malloc(sz);

Mógłbyś pomyśleć, że alokuje 2 porcje pamięci, ale w Twoim umyśle są to „struktury”, „niektóre bufory”. Ale malloc w ogóle tego nie widzi.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się