2009-10-26
printpaper.org
Ponad miesiąc temu uruchomiłem nowy projekt: printpaper.org.
Główna funkcja już jest i działa. Mam jeszcze sporo pomysłów do zrealizowania (w tym wersje językowe), ale muszę się przy tym bardzo powstrzymywać, bo staram się utrzymać minimalny charakter tej usługi. Informacje o nowościach będą się pojawiać na Twitterze na kanale printpaperorg.
Wesołego drukowania!
2009-10-03
helper.h
Sporo piszę w C (kod poniżej dotyczy C99). W międzyczasie urodziłem kilka jednolinijkowych makr mających przede wszystkim zwiększyć komfort pisania i przejrzystość kodu; służą głównie do alokacji pamięci.
Najczęściej używam NEW(type, n) dzięki któremu zamiast pisać:
wystarczy, że napiszę:
Są też NEW_CLEAR(t, n) i RESIZE(t, n), które odpowiadają calloc(..) i realloc(..). Niby nic wielkiego, ale zauważyłem, że często tego używam i dużo przyjemniej się pracuje nad kodem - właśnie dlatego postanowiłem się tym podzielić.
Do ściągnięcia: helper.tar.gz
Nie jestem przekonany co do tej nazwy - jest zbyt ogólna; ale to jest takie małe i ma w sobie prawie tylko proste, pomocnicze makra, że tylko taka mi pasowała.
Dodatkowo przy wstępnym portowaniu kodu z języków, w których tablice mają indeksy startujące od 1, a nie od 0 zrobiłem NEW1 i FREE1; Np:
Potem oczywiście lepiej zmodyfikować kod do normalnych indeksów, ale przydają się po to żeby na początku uniknąć błędów przy modyfikacjach w locie i skupić się na innych możliwych błędach.
Niedawno rozszerzyłem to o makra alokujące tablice, które pamiętają ilość elementów. Dzięki temu można napisać:
To jest świeże i kod dotyczący tych tablic nie jest jeszcze dobrze przetestowany - czekam na bugi i pomysły. Zależało mi na tym, żeby to było możliwie proste, oszczędne i żeby tablice te bez problemu działały z normalnymi funkcjami, które oczekują na wejściu wskaźnika. Główną motywacją było to, że nie znoszę przechowywać wielkości tablicy w osobnej zmiennej i jeszcze pamiętać jak się ona nazywa (oraz pisać funkcji, które obok argumentu typu wskaźnikowego mają kolejny argument z ilością elementów). Ogólnie można by całą ideę rozszerzyć i zapisywać trochę więcej informacji oprócz ilości elementów i zaimplementować niezły Vector, ale to wydaje się już za bardzo kłócić z zakładaną prostotą.
Działa to tak: ARRAY_NEW zwraca normalny wskaźnik zadeklarowanego typu. Dokładniej ARRAY_NEW alokuje normalną tablicę+miejsce na jeden size_t, zapisuje długość tablicy w size_t na początku i zwraca wskaźnik już do samych danych (czyli +sizeof(size_t)). Trzeba tylko pamiętać, żeby taką tablicę zwolnić za pomocą ARRAY_FREE.
Pętlę for taką jak kawałek wyżej można uprościć za pomocą makra FOREACH:
Wśród makr jest też ARRAY_RESIZE(array, new_size). Służy do zmiany rozmiaru tablicy alokowanej przy pomocy ARRAY_NEW. Przykład:
Ponieważ są to normalne wskaźniki można ich normalnie używać jak każdych innych i napisać np:
lub trochę krócej:
Dziś dodałem też makro ACCESS (ARRAY_ACCESS), które kłóci się trochę z resztą kodu, bo psuje czytelność, ale ma taką zaletę, że wplata assert(..) (z assert.h) dbający o dostęp tylko w ramach zaalokowanej pamięci na elementy.
Żeby tego użyć zamiast:
Należy napisać:
Jako jedno z zastosowań widzę implementacje złożonych algorytmów, które trudno się debuguje.
Należy jednak pamiętać, że ACCESS to makro i w tym wypadku nie powinno się podawać do niego argumentów, które mają efekty uboczne, bo zostaną wywołane kilka razy.
ACCESS korzysta z assert z assert.h, dlatego jeżeli doda się na początku programu #define NDEBUG (przed pierwszym #include lub jako parametr dla kompilatora), to w miejscach użycia ACCESS wygeneruje się kod tak samo szybki jak przy normalnych odwołaniach do tablicy.
Makra RESIZE i ARRAY_RESIZE w obecnej formie korzystają z niestandardowego operatora __typeof__ i mogą nie działać pod czymś innym niż gcc. Reszta kodu wymaga tylko malloc, calloc, realloc (oraz memset dla makr ZERO i ARRAY_ZERO, które służą do czyszczenia tablic).
Najczęściej używam NEW(type, n) dzięki któremu zamiast pisać:
SomeType *table = (SomeType *)malloc(sizeof(SomeType)*1000);
wystarczy, że napiszę:
SomeType *table = NEW(SomeType, 1000);
Są też NEW_CLEAR(t, n) i RESIZE(t, n), które odpowiadają calloc(..) i realloc(..). Niby nic wielkiego, ale zauważyłem, że często tego używam i dużo przyjemniej się pracuje nad kodem - właśnie dlatego postanowiłem się tym podzielić.
Do ściągnięcia: helper.tar.gz
Nie jestem przekonany co do tej nazwy - jest zbyt ogólna; ale to jest takie małe i ma w sobie prawie tylko proste, pomocnicze makra, że tylko taka mi pasowała.
Dodatkowo przy wstępnym portowaniu kodu z języków, w których tablice mają indeksy startujące od 1, a nie od 0 zrobiłem NEW1 i FREE1; Np:
double *vec = NEW1(double, 2000);
vec[1] = 12323; // pierwszy element
vec[2000] = 33; // ostatni element
// vec[0] = 232; - błąd
vec[1] = 12323; // pierwszy element
vec[2000] = 33; // ostatni element
// vec[0] = 232; - błąd
Potem oczywiście lepiej zmodyfikować kod do normalnych indeksów, ale przydają się po to żeby na początku uniknąć błędów przy modyfikacjach w locie i skupić się na innych możliwych błędach.
Niedawno rozszerzyłem to o makra alokujące tablice, które pamiętają ilość elementów. Dzięki temu można napisać:
double *v = ARRAY_NEW(double, 1000);
//...
for (int i = 0; i < LEN(v); ++i) {
v[i] = sin(2.*M_PI*i/LEN(v));
}
//...
ARRAY_FREE(v);
//...
for (int i = 0; i < LEN(v); ++i) {
v[i] = sin(2.*M_PI*i/LEN(v));
}
//...
ARRAY_FREE(v);
To jest świeże i kod dotyczący tych tablic nie jest jeszcze dobrze przetestowany - czekam na bugi i pomysły. Zależało mi na tym, żeby to było możliwie proste, oszczędne i żeby tablice te bez problemu działały z normalnymi funkcjami, które oczekują na wejściu wskaźnika. Główną motywacją było to, że nie znoszę przechowywać wielkości tablicy w osobnej zmiennej i jeszcze pamiętać jak się ona nazywa (oraz pisać funkcji, które obok argumentu typu wskaźnikowego mają kolejny argument z ilością elementów). Ogólnie można by całą ideę rozszerzyć i zapisywać trochę więcej informacji oprócz ilości elementów i zaimplementować niezły Vector, ale to wydaje się już za bardzo kłócić z zakładaną prostotą.
Działa to tak: ARRAY_NEW zwraca normalny wskaźnik zadeklarowanego typu. Dokładniej ARRAY_NEW alokuje normalną tablicę+miejsce na jeden size_t, zapisuje długość tablicy w size_t na początku i zwraca wskaźnik już do samych danych (czyli +sizeof(size_t)). Trzeba tylko pamiętać, żeby taką tablicę zwolnić za pomocą ARRAY_FREE.
Pętlę for taką jak kawałek wyżej można uprościć za pomocą makra FOREACH:
FOREACH(i, v) {
v[i] = sin(2.*M_PI*i/LEN(v));
}
v[i] = sin(2.*M_PI*i/LEN(v));
}
Wśród makr jest też ARRAY_RESIZE(array, new_size). Służy do zmiany rozmiaru tablicy alokowanej przy pomocy ARRAY_NEW. Przykład:
double *tab = ARRAY_NEW(double, 100);
//...
printf("%d\n", LEN(tab)); // drukuje "100"
ARRAY_RESIZE(tab, 1024);
tab[1023] = 123; // ok
printf("%d\n", LEN(tab)); // drukuje "1024"
//...
printf("%d\n", LEN(tab)); // drukuje "100"
ARRAY_RESIZE(tab, 1024);
tab[1023] = 123; // ok
printf("%d\n", LEN(tab)); // drukuje "1024"
Ponieważ są to normalne wskaźniki można ich normalnie używać jak każdych innych i napisać np:
write(fd, tab, sizeof(*tab)*LEN(tab));
lub trochę krócej:
write(fd, tab, SIZE(tab));
Dziś dodałem też makro ACCESS (ARRAY_ACCESS), które kłóci się trochę z resztą kodu, bo psuje czytelność, ale ma taką zaletę, że wplata assert(..) (z assert.h) dbający o dostęp tylko w ramach zaalokowanej pamięci na elementy.
Żeby tego użyć zamiast:
tab[i] = tab[i - 2] + tab[i - 1];
Należy napisać:
ACCESS(tab, i) = ACCESS(tab, i - 2) + ACCESS(tab, i - 1);
Jako jedno z zastosowań widzę implementacje złożonych algorytmów, które trudno się debuguje.
Należy jednak pamiętać, że ACCESS to makro i w tym wypadku nie powinno się podawać do niego argumentów, które mają efekty uboczne, bo zostaną wywołane kilka razy.
ACCESS korzysta z assert z assert.h, dlatego jeżeli doda się na początku programu #define NDEBUG (przed pierwszym #include lub jako parametr dla kompilatora), to w miejscach użycia ACCESS wygeneruje się kod tak samo szybki jak przy normalnych odwołaniach do tablicy.
Makra RESIZE i ARRAY_RESIZE w obecnej formie korzystają z niestandardowego operatora __typeof__ i mogą nie działać pod czymś innym niż gcc. Reszta kodu wymaga tylko malloc, calloc, realloc (oraz memset dla makr ZERO i ARRAY_ZERO, które służą do czyszczenia tablic).
Starsze posty dostępne w archiwum