Bezpieczny malloc w Ansi C

Funkcja malloc jest przyczyną wielu ciężkich do wykrycia błędów. Wystarczy zapomnieć o pomnożeniu przez rozmiar elementu i bug gotowy. Dodatkowo w starszych kompilatorach wartość zwracaną przez malloc trzeba rzutować do określonego typu co na dłuższą metę jest bardzo pracochłonne i niewygodne. Tak mniej więcej wygląda standardowe wywołanie funkcji:

Oczywiście zamiast sizeof(unsigned long int*) można wpisać 4 ale zmniejsza to przenośność kodu - w końcu niekoniecznie na wszystkich komputerach i nie na wszystkich systemach wskaźniki muszą mieć rozmiar 4 bajtów.
W powyższym zapisie bardzo łatwo o pomyłkę a i czytelność jest znikoma. Jednak jest sposób aby unknąć wszystkich wyżej wymienionych problemów z malloc: z pomocą przychodzi nam preprocesor języka C. Bardzo prosto napisać makro, które wykona za nas rzutowanie oraz mnożenie przez rozmiar elementu. Poniższe makro jako argumenty przyjmuje typ elementów oraz ilość elementów do przydzielenia

#define MALLOC(typ, ilosc) (typ*)malloc((ilosc)*sizeof(typ))
 


Makra uzywa się w ten sposób:



Prawda, że czytelniejsze? ;>

Kolejnym błędem podczas przydziału pamięci jest nie sprawdzanie czy pamięć została przydzielona (komu się chce za każdym razem pisać
if(p == NULL) {fprintf(stderr, "malloc error"); exit(0);}
). Na to również znajdzie się sposób: wystarczy zdefiniować funkcję, która będzie to sprawdzała:

void* _new(size_t rozmiar)
{
     void* p = malloc(rozmiar);
     if(p == NULL)
     {
           fprintf(stderr, "Out of memory!");
           exit(EXIT_FAILURE);
     }
      return p;
}


oraz troche przerobić nasze makro:

#define MALLOC(typ, ilosc) (typ*)_new((ilosc)*sizeof(typ))
 


Jeszcze jednym popełnianym błędem może być używanie malloc z argumentami naszego MALLOC (np. z przyzwyczajenia)
można temu tak zaradzić:
/*---plik my_alloc.h---*/
#define MALLOC(typ, ilosc) (typ*)_new((ilosc)*sizeof(typ))
#define malloc NIE_UŻYWAJ_BEZPOŚREDNIO_malloc!

extern void* _new(size_t);
/*---EOF---*/

/*---plik my_alloc.c---*/
#include <stdlib.h>
#include <stdio.h>
#include "my_alloc.h"
#undef malloc

void* _new(size_t rozmiar)
{
     void* p = malloc(rozmiar);
     if(p == NULL)
     {
           fprintf(stderr, "Out of memory!");
           exit(EXIT_FAILURE);
     }
      return p;
}
/*---EOF---*/

/*---plik main.c---*/
#include "my_alloc.h"

int main(int argc, char *argv[])
{
    unsigned long int **p = MALLOC(unsigned long int*, 12);
    int *b = malloc(12); /*---Błąd!---*/
    return 0;
}
/*---EOF---*/


Całkiem niewielkim nakładem pracy mamy bezpieczny i wygodny system przydziału pamięci podobny do new z C++.

Jeżeli artykuł Wam się spodobał to następnym razem napisze coś o programowaniu obiektowym w C.
Informacje
Ostatnia modyfikacja 26-11-2004 13:10 Ostatni autor Heimdall
Ilość wyświetleń 10752 Wersja 1
Komentarz
skiter dnia 19-01-2007 05:15
Chym a moze kozystac z perror i jemu podobnych chym? ...
Gynvael Coldwind dnia 26-07-2006 18:40
fprintf(stderr, "Out of memory!");
exit(EXIT_FAILURE);

Imho to zły pomysł. Nie wszystkie OS'y czyszczą heap po procesie gdy on wychodzi (np win9x nie czyści). A "exit" tam uniemożliwia programiście wyczyszczenie pamięci przed wyjściem z błędem.
Co innego gdyby była jakaś globalna lista (drzewo binarne najlepiej) zaalokowanych fragmentów pamięci, do której _new by wrzucał po prostu wskaźniki przy alokacji, a jakiś _delete by usuwał je. Można by wtedo "atexit" poprosić o wywołanie funkcji dealokującej pamięć (mamy wkońcu wszystko zaalokowane na liście/drzewie), więc to "exit" tam by było całkiem OK ;>
Adam Boduch dnia 27-11-2004 12:42
test

Katalog
Copyright © 2000-2006 by Coyote Group 0.9.3-pre3
Czas generowania strony: 0.1745 sek. (zapytań SQL: 10)