lapsus alumni

styczeń 21, 2007

Enter Valhalla

Zaszufladkowany do: C, Programowanie — elmopl @ 8:49 pm

Jedną z ważniejszych umiejętności przy pisaniu programów jest sprawdzanie czy aby program działa tak jak powinien. Bywają w końcu błędy, które nie powodują wyłożenia się programu od razu tylko musi pochodzić dłuższy czas, a nawet i wtedy wcale nie musi być jednoznaczne, że zatkanie się systemu jest efektem działania naszego programu, a nie jakiegoś innego.
W końcu przy bardziej rozbudowanych programach odkryć wyciek pamięci wcale nie jest łatwo. Szczególnie jak mamy małe strukturki po kilka bajtów przez co nie zwracają na siebie uwagi. Zapomnieć o zwolnieniu takiego ustrojstwa jest dość łatwo. Czasem też się zdarza skorzystać ze zmiennej automatycznej bez jej inicjalizacji co może prowadzić do bardzo nieoczekiwanych rezultatów. Na szczęście mądrzy ludzie wymyślili program, który pomaga w radzeniu sobie w dużym stopniu z wieloma błędami. A nazwali go Valgrind
(więcej…)

styczeń 13, 2007

Wskaźnik na wskaźnik. A po co?

Zaszufladkowany do: C, Programowanie — elmopl @ 5:40 pm

#include
#include 

void func1(int *b){
   b = malloc(sizeof(int));
   *b = 1;

   return;
}

void func2(int **c){
   *c = malloc(sizeof(int));
   **c = 2;

   return;
}

int main(int argc, char **argv){
   int *a = NULL;

   a = (int*) malloc(sizeof(int));
   *a = 0;

   printf("%d\n", *a);

   func1(a);
   printf("%d\n", *a);

   func2(&a);
   printf("%d\n", *a);

   free(a);

   return 0;
}

Odpowiedzi dostarczy analiza tego krótkiego programiku.
Przy okazji ten program zawiera błąd w postaci wycieku pamięci. W drugiej funkcji tracimy wskaźnik na zaalokowaną pamięć nie zwalniając jej.

styczeń 3, 2007

Zmienne globalne w C

Zaszufladkowany do: C, Programowanie — elmopl @ 10:48 pm

Tak jak wcześniej napisałem opiszę swoje doświadczenia z korzystaniem ze zmiennych globalnie dostępnych.
Jeżeli planujemy pisać dłuższe programy dobrze jest wyrobić sobie odpowiednie nawyki. Jednym z nich jest rozważne używanie zmiennych globalnych. To co mnie dziwi w przykładach dawanych na wykładach jest wykorzystywanie do wszystkiego zmiennych globalnych.
Weźmy na przykład jeden z projektów na ćwiczenia z programowania. Napisać stos przechowujący elementy dowolnego typu.
Według podejścia “wykładowego” w programie takim pojawiłaby się zmienna globalna np.

struct {
   int pole1;
   int pole2;
   [...]
} stack;

int stackPop(void *elem){
   /* coś tam pobierającego, wybierającego i usuwającego */

   elem = lastElement
   return 0;
}

int stackPush(void *elem, size_t size){
   /* dodawanie */

   return 0;
}

int main(int argc, char **argv){
   /* tutaj może jakaś inicjalizacja by się pojawiła */

   stackPush(to_add, sizeof(to_add));

   if(stackPop(getEelem) == 0){
      free(getEelem);
   }

   return 0;
}

Teraz wyobraźmy sobie, że przydałoby się w tym samym albo w innym programie stworzyć kilka stosów. Ale jak to zrobić jeżeli funkcje korzystają wprost ze zmiennej globalnej? Ano trzeba wszystko przeedytować na to jak można było na samym początku napisać:

struct stack_struct {
   int pole1;
   int pole2;
   [...]
};

int stackPop(struct stack_struct *stack, void *elem){
   /* coś tam pobierającego, wybierającego i usuwającego */
   elem = lastElement;
   return 0;
}

int stackPush(struct stack_struct *stack, void *elem){
   /* dodawanie */

   return 0;
}

int main(int argc, char **argv){
   /* tutaj może jakaś inicjalizacja by się pojawiła */

   stackPush(stack1, to_add1, sizeof(to_add1));
   stackPush(stack1, to_add2, sizeof(to_add2));
   stackPush(stack2, to_add3, sizeof(to_add3));

   if(stackPop(stack1, getEelem) == 0){
      free(getEelem);
   }

   return 0;
}

Używanie zmiennych globalnych naprawdę bardzo sporadycznie jest przydatne, a jeszcze rzadziej jest potrzebne.
Jeszcze jedna różnica z wykładowymi programami to tak, że ja nie używam typedef’ów. Jedno co moge o takowych powiedzieć to to, że tworzenie typów wskaźnikowych jest głupotą/masochizmem. A jedyna różnica o jakiej wiem pomiędzy

typedef struct {
[...]
} aaa;

a

struct aaa {
[...]
};


jest przyzwyczajenie. Dla mnie czytelniejszy i wygodniejszy jest drugi zapis. Choć może typedef ma jeszcze jakieś ukryte właściwości, o których nie było mi dane do tej pory się dowiedzieć.

grudzień 29, 2006

Porgram “find” z wykładu

Zaszufladkowany do: C, Programowanie, Studia, Wykłady — elmopl @ 4:46 pm

Na wykładzie został przedstawiony program w ramach tematu o obsłudze plików.
Skopiowane niezbędne minimum

#define MAXLINE 1000
#define MAXSEARCH 64
void search(FILE *file, char *searchstr, int ignore){
   char li_cpy[MAXLINE];
   char line[MAXLINE];
   long linum = 0;
   char *found;

   if(ignore) _strupr(searchstr);
   while( fgets(line, MAXLINE-1, file) ){
      ++linum;
      if(ignore)
         found = strstr(_strupr(strcpy(li_cpy, line)), searchstr);
      else
         found = strstr(line, searchstr);
      if( found != NULL ){
         if(number) printf("%8d: ", linum);
         printf("%s", line);
      }
   }
}

Błąd znajduje się w części merytorycznej nie związanej bezpośrednio z głównym tematem wykładu.

Rozważmy plik

aaabaa
baaaba
a...ab

W którym w ostatniej linii znajduje się 1000 znaków “a”. Funkcja zadziała poprawnie dla pierwszych dwu linii, ale dla ostatniej pominie wszystko po 999 ‘a’ włącznie.
Z tego przykładu idzie bardzo prosta nauka: trzeba uwzględniać, że dane użytkownika zawsze mogą być “złośliwe”.

Ogólnie od siebie mogę powiedzieć, że używanie zmiennych globalnych (w tym define’ów) przy większych (pisanych dłużej jak tydzień i z ewentualnymi potrzebami późniejszych zmian) projektach jest kiepskim pomysłem. Jak znajdę jakiś zwięzły i przejrzysty przykład to dokładniej opiszę czemu.

#define TEXT “jakiś tam napis”, a co z pamięcią?

Zaszufladkowany do: C, Programowanie, Studia, Wykłady — elmopl @ 1:15 pm

Na wykładach jednym z przykładów, który mnie zaintrygował były takie oto linijki kodu:

char *tab_w[5];
tab_w[0] = "To jest tekst na pozycji 0";
#define TKST1 "To jest tekst na pozycji 1";
tab_w[1] = (char *) malloc(strlen(TKST1)+1);
strcpy(tab_w[1], TKST1);

Zastanowiło mnie czy to aby nie zajmie dwa razy za dużo pamięci. Po krótkich testach w domu okazuje się, że (przynajmniej przy kompilacji gcc) jest to zgrabnie optymalizowane.
textAsMacro.c:

#include
#include
#include 

#define TEXT "aaa"

int main(int argc, char **argv){
   printf("%d\n", (int) TEXT);
   printf("%d\n", (int) TEXT);

   printf("%d\n", (int) "aaa");

   return 0;
}

Po wywołaniu:

$ gcc -o textAsMacro.e textAsMacro.c
$ ./textAsMacro.e
134513800
134513800
134513800

Działa to w obrębie tylko jednego pliku z dość oczywistych względów.

Blog na WordPress.com.