Lab 2 - Valgrind + styl kodowania — IPP 2011/12

Spis treści

Poprzedni temat

Lab 1 - Bash i make

Następny temat

Lab 3 - moduły + PasDoc

Lab 2 - Valgrind + styl kodowania

Valgrind

Valgrind to narzędzie do usuwania problemów związanych z zarządzaniem dynamicznie alokowaną pamięcią. Wczytuje i wykonuje instrukcje analizowane programu, gromadząc przy tym dane potrzebne do wykrycia ewentualnych błędów. Może także działać jako profiler, zbierający informacje o wykorzystaniu pamięci podręcznych procesora, albo program wykrywający typowe problemy w aplikacjach wielowątkowych.

Żeby wykorzystać Valgrind do analizy programu napisanego w Pascalu trzeba podczas kompilacji podać opcję ‘-v’.

$ ppcx64 -gv -O- program.pas

W powyższym przykładzie ‘-O-‘ to nie meksykanin na rowerze widziany z góry, lecz opcja wyłączająca optymalizację.

Przylad uruchomienia programu:

$ ./valgrind --leak-check=full ./program

Ponieważ instrukcje są intepretowane przez Valgrind, a nie wykonywane bezpośrednio przez procesor, należy się spodziewać znacznego zmniejszenia wydajności programu (ok. 20-30x).

Poniżej przedstawiono kilka przykładów błędów, które może wykryć Valgrind.

Przykład - prosty wyciek

Poniższy program alokuje dwa bloki pamięci, ale zwalnie tylko jeden (adres pierwszego bloku jest zamazywany w drugim wywyołaniu ‘New’).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
program simpleleak;

{$mode objfpc}{$H+}

procedure Test;
var
   Ptr : ^Integer;
begin
   New(Ptr);
   New(Ptr);    { Poprzedni wskaźnik zostanie nadpisany. }
   Dispose(Ptr);
end;

begin
   Test;
end.

Wynik analizy przeprowadzonej przez Valgrind:

tsznuk@grzebien:~/ipp$ ppcx64 -O- -gv simpleleak.pas
tsznuk@grzebien:~/ipp$ valgrind --leak-check=full ./simpleleak
==2490== Memcheck, a memory error detector
==2490== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2490== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==2490== Command: ./simpleleak
==2490==
==2490==
==2490== HEAP SUMMARY:
==2490==     in use at exit: 12 bytes in 1 blocks
==2490==   total heap usage: 2 allocs, 1 frees, 24 bytes allocated
==2490==
==2490== 12 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2490==    at 0x4C2779D: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2490==    by 0x422CF6: CMEM_CGETMEM$QWORD$$POINTER (in /home/tsznuk/ipp/simpleleak)
==2490==    by 0x4046AD: main (simpleleak.pas:15)

Jak widać wyciek pamięci został wykryty. Co więcej, Valgrind wydrukował ciąg wywołań, który doprowadził do zaalokowania zgubionego bloku. Co prawda w wydrukowanym śladzie brakuje kluczowego wpisu (funkcji Test i numeru linii: 9), ale musimy mieć na uwadze, że Valgrind i FPC to programy darmowe i dostajemy, za co zapłaciliśmy.

Przykład - podwójna dealokacja

Poniższy program usiłuje dwukrotnie zdealokować ten sam blok pamięci.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
program doublefree;

{$mode objfpc}{$H+}

procedure Test;
var
   Ptr : ^Integer;
begin
   New(Ptr);
   Dispose(Ptr);
   Dispose(Ptr);   { Podwójne wywołanie. }
end;

begin
   Test;
end.

Wynik analizy przeprowadzonej przez Valgrind:

tsznuk@grzebien:~/ipp$ ppcx64 -O- -gv doublefree.pas
tsznuk@grzebien:~/ipp$ valgrind --leak-check=full ./doublefree
==2513== Memcheck, a memory error detector
==2513== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2513== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==2513== Command: ./doublefree
==2513==
==2513== Invalid free() / delete / delete[]
==2513==    at 0x4C268FE: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2513==    by 0x422D14: CMEM_CFREEMEM$POINTER$$QWORD (in /home/tsznuk/ipp/doublefree)
==2513==    by 0x40469D: main (doublefree.pas:15)
==2513==  Address 0x51b4040 is 0 bytes inside a block of size 12 free'd
==2513==    at 0x4C268FE: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2513==    by 0x422D14: CMEM_CFREEMEM$POINTER$$QWORD (in /home/tsznuk/ipp/doublefree)
==2513==    by 0x40469D: main (doublefree.pas:15)

Valgrind wykrył podwójną dealokacje i wypisał ślad obu wywołań ‘Dispose’. Niestety niekompletny, tak jak w poprzednim przykładzie.

Przykład - przedwczesna dealokacja

Program dealokuje blok pamięci, a potem zapisuje do niego wartość.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
program earlyfree;

{$mode objfpc}{$H+}

procedure Test;
var
   Ptr : ^Integer;
begin
   New(Ptr);
   Dispose(Ptr);
   Ptr^ := 42;   { Pamięć została przed chwilą zwolniona. }
end;

begin
   Test;
end.

Wynik analizy przeprowadzonej przez Valgrind:

tsznuk@grzebien:~/ipp$ ppcx64 -O- -gv earlyfree.pas
tsznuk@grzebien:~/ipp$ valgrind --leak-check=full ./earlyfree
==2527== Memcheck, a memory error detector
==2527== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2527== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==2527== Command: ./earlyfree
==2527==
==2527== Invalid write of size 4
==2527==    at 0x404688: P$DOUBLEFREE_TEST (earlyfree.pas:11)
==2527==    by 0x40469D: main (earlyfree.pas:15)
==2527==  Address 0x51b4048 is 8 bytes inside a block of size 12 free'd
==2527==    at 0x4C268FE: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2527==    by 0x422D14: CMEM_CFREEMEM$POINTER$$QWORD (in /home/tsznuk/ipp/earlyfree)
==2527==    by 0x40469D: main (earlyfree.pas:15)

Valgrind odkrył niepoprawny zapis. Wykrył też, że pamięć, którą program usiłował modyfikować, była wcześniej zwolniona - i wypisał również sład prowadzący do wykonania owej dealokacji. Co ciekawe, ślad zapisu zaiwera dokładny i poprawny numer linii.

Przykład - przekroczenie zakresu

Przykład klasycznego błędu - odwołanie do dynamicznie zaalokowanej tablicy z przekroczeniem zakresu.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
program overflow;

{$mode objfpc}{$H+}

procedure Test;
const
   N = 10;
type
   TNumberArray = array [0 .. N - 1] of Integer;
   PNumberArray = ^TNumberArray;
var
   Numbers : PNumberArray;
   i : Integer;
begin
   GetMem(Numbers, SizeOf(TNumberArray));
   for i := 1 to N do  { Powinno być 0 .. N - 1 }
       Numbers^[i] := 42;
   FreeMem(Numbers);
end;

begin
   Test;
end.

Wynik analizy przeprowadzonej przez Valgrind:

tsznuk@grzebien:~/ipp$ ppcx64 -O- -gv overflow.pas
tsznuk@grzebien:~/ipp$ valgrind --leak-check=full ./overflow
==2554== Memcheck, a memory error detector
==2554== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2554== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==2554== Command: ./overflow
==2554==
==2554== Invalid write of size 4
==2554==    at 0x404648: P$OVERFLOW_TEST (overflow.pas:17)
==2554==    by 0x404675: main (overflow.pas:22)
==2554==  Address 0x51b405c is 0 bytes after a block of size 48 alloc'd
==2554==    at 0x4C2779D: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2554==    by 0x422CB6: CMEM_CGETMEM$QWORD$$POINTER (in /home/tsznuk/ipp/overflow)
==2554==    by 0x404675: main (overflow.pas:22)

Valgrind wykrył zapis do niezaalokowanej pamięci. Dodatkowo znalazł najbliższy poprawnie zaalokowany blok, co jest pomocne przy diagnozowaniu błędu.

Moduł heaptrc

Free Pascal zawiera własny mechanizm do wykrywania wycieków pamięci - moduł heaptrc. Ma mniejsze możliwości, niż Valgrind, ale jest bardziej wydajny. Żeby z niego skorzystać, trzeba skompilować program z opcją ‘-gh’. Dodanie opcji ‘gl’ pozwoli nam odczytać numery linii z końcowego raportu (który powstanie po uruchomieniu programu).

Przykład

[tsznuk@duch ipptmp]$ ppcx64 -gh -gl simpleleak.pas
[tsznuk@duch ipptmp]$ ./simpleleak
Heap dump by heaptrc unit
2 memory blocks allocated : 8/16
1 memory blocks freed     : 4/8
1 unfreed memory blocks : 4
True heap size : 32768
True free heap : 32608
Should be : 32632
Call trace for block $00007F6C539F20C0 size 4
  $000000000040022E line 15 of simpleleak.pas
  $00000000004001B8

Styl kodowania

Na użytek zajęć z IPP standardem kodowania nazwiemy zestaw reguł, których stosowanie podczas tworzenia kodu programu służy (przynajmniej w teorii) zwiększeniu czytelności owego kodu. Zakres zagadnień, które opisuje taki standard, zależy od konkretnego projektu. Poniżej przedstawiono przykłady reguł i kwestii, które zwykle reguluje konwencja kodowania.

Przykłady reguł

Ostrzeżenie

Poniższy rozdział zawiera losowe przykłady reguł, pochodzących z różnych standardów. Nie należy traktować podanych tu zasad jako spójnej konwencji kodowania.

Nazewnictwo - sposób nazywania zmiennych, typów, pól, plików etc.
  • Nazwy stałych pisane są wielkimi literami, z podkreśleniami.
  • W nazwach zmiennych obowiązuje camelCase, PascalCase, ...
  • Nazwy procedur zaczynają się od wielkiej litery.
  • Nazwy typów zaczynają się od prefiksu ‘T’.
  • Notacja węgierska
Wcięcia
  • Użycie k spacji - albo tabów
  • W konstrukcji ‘IF ... THRN BEGIN ... END’ słowo ‘BEGIN’ ma być w nowej linii, wcięte tak jak ‘IF’
  • Przypadki w instrukcji ‘CASE’ mają być wcięte o jeden poziom głębiej niż słowo ‘CASE’
Odstępy
  • Spacja występuje zawsze po przecinku, ale nigdy przed.
  • Nie ma spacji między nazwą funkcji a argumentami.
  • Zbyt długie wyrażenia łamiemy przed/po danym operatorze.
Komentarze
  • Używamy takich lub innych znaków komentarza.
  • Wszystkie metody widoczne na zewnątrz modułu mają komentarz.
Konstrukcje językowe
  • Goto nie może być stosowane
  • Albo może, ale tylko do opuszczania zagnieżdżonych pętli
  • Po konstrukcji ‘IF ... THEN’ zawsze stosujemy ‘BEGIN’
Kolejność deklaracji
  • W pliku najpierw deklarujemy stałe, potem typy, zmienne i procedury
  • Wszystkie procedury muszą być predeklarowane
  • Kolejność poziomów dostępu w deklaracji klasy: private/protected/public/published
Inne
  • Nie używamy zmiennych globalnych.
  • Pola obiektu zawsze są prywatne.
  • Wszystkie stałe napisowe są tłumaczone przez GetText lub oznaczone stosowną adnotacją.
  • ...

Uwagi

Todo

Uzupełnić

Linki

Konwencje opracowane przez Borland/Inprise/CodeGear/Embarcadero i rozwinięte przez projekt JEDI:

http://wiki.delphi-jedi.org/index.php?title=Style_Guide

Zestaw reguł i uwag z dokumentacji kompilatora GPC:

http://www.gnu-pascal.de/h-gpcs-en.html

Jeszcze jeden standard (Delphi 4 Developer’s Guide)

http://www.econos.de/delphi/cs.html

C/C++

Przykład standardu kodowania dla C++:

http://www2.research.att.com/~bs/JSF-AV-rules.pdf