Analiza konstrukcji zawierających wskaźniki
Transkrypt
Analiza konstrukcji zawierających wskaźniki
Analiza konstrukcji zawierających wskaźniki Piotr Błaszyński Wskaźniki ● podejście naiwne: while(ptr!=NULL){ a[i] = *ptr; i++; ptr++; } po zmianie: N=length(ptr); alias_ptr = ptr; for(j=0 ; j<N ; j++){ a[i] = alias_ptr[j]; i++; } Wskaźniki ● czy kod o złożonej strukturze operacji na wskaźnikach można zrównoleglić: ○ struktury danych o swobodnych powiązaniach (heap modelling) ○ struktury danych odworowujące kolekcje, przypominające tablice (aggregate modelling) ○ aliasy do innych miejsc w pamięci (alias representation). ● restrict (nowe słowo kluczowe C99). Przykład x := 5 ptr := @x *ptr := 9 y := x progra m S 1 S 2 S 3 S 4 zależności ● Problem: Nie można uzyskać informacji o zależnościach patrząc jedynie na nazwy zmiennych, bo: ○ po wykonaniu wyrażenia S2, zmienne x i ptr wskazują na to samo miejsce w pamięci, ○ czyli ptr wskazuje (points-to) na x po wykonaniu S2. ● W językach podobnych do C i posiadających wskaźniki, potrzebna jest wiedza o powyższych relacjach żeby prawidłowo określic zależności Propozycja rozwiązania ● Dla uproszczenia tylko 2 typy:int i int* ● Brak sterty, wszystkie wskaźniki wskazują tylko na zmienne znajdujące się na stosie ● Brak wywołań funkcji ● Jedyne wyrażenia wpływające na zmienne wskaźnikowe: ○ wyłuskanie adresu: x = &y ○ kopia: x = y ○ załadowanie wartości: x = *y ○ przechowanie wartości: *x = y ● Obliczenia też dają w wyniku int Graf zależności ● Graf skierowany (z grotami strzałek): ○ węzły to zmienne ○ krawędzie (a,b): zmienna a wskazuje na zmienną b ○ pt ○ r ○ x ○ y ○ ○ ○ ● specjalny węzeł na NULL ● Graf ten wygląda różnie w różnych miejscach programu Graf zależności ● Węzeł może mieć więcej niż 1 krawędź wyjściową ○ jeżeli graf zależności ma krawędzie (a,b) i (a,c), to znaczy, że zmienna a może wskazywać zarówno na zmienną b jak i c ○ w zależności od tego w jaki sposób program dotarł do tego punktu jedno z tych wskazań może być prawdziwe ○ analiza z uwzględnieniem ściezki wykonania (path-sensitive) jest trudna do wykonania (statycznie) p if (p) x = &y else x := &z ….. x = &y Na co wskazuje teraz x? x = &z Graf zależności - porządkowanie ● Porządkowanie podzbioru zmiennych: ○ Najmniejszym elementem jest graf bez krawędzi, ○ G1 <= G2 jeżeli graf G2 ma wszystkie krawędzie grafu G1 i ewentualnie jakies dodatkowe ○ G1 U G2 najmniejszy graf który zawiera wszystkie krawedzie G1 i G2 Analiza Trzy sposoby (algorytmy): ● Analiza zależna od przepływu ○ Tak naprawdę analiza przepływu danych ○ Wyznacza się w niej dokładne wskazania w poszczególnych momentach programu (wiele grafów) Analiza ● Analiza niezależna od przepływu ○ Wyznacza się jeden graf dla całego programu ○ Algorytm Andersena ■ Jest naturalnym uproszczeniem algorytmów zależnych od przepływu ○ Algorytm Steensgarda ■ Węzły w grafie są grupowane (z kilku zmiennych, pomiędzy którymi zachodzi równoważność) ■ jeżeli x może wskazywać zarówno na y i z, to wtedy y i z są traktowane jako równoważne (umieszcza się je w klasie równoważności) ■ Graf zależności ma krawędzie od "potomków" do "rodziców" ■ Mniej dokładny (od alg. Andersena), ale szybszy Wskaźniki ptr x z y w ptr x z y x := &z Andersen ptr := &x y := &w ptr := &y pt r x, z,w y Steensgard w Struktury ● Przykładowa struktura ○ struct cell {int value; struct cell *left, *right;} ○ struct cell x,y; ● Podejście uzwględniające pola ○ x i y są węzłami ○ każdy węzeł ma 3 wewnetrzne pola: value, left, right ● Taka reprezentacja uzwględnia wskazania do wnętrza struktur ○ Jeżeli to nie jest konieczne, można po prostu przyjąć, że pojedynczy węzeł przesdtawia jedną strukturę i tylko opisywać krawędzie przy pomocy nazw pól Przykład int main(void) { struct cell {int value; struct cell *next; }; struct cell x,y,z,*p; int sum; x.value = 5; x.next = &y; y.value = 6; y.next = &z; z.value = 7; z.next = NULL; p = &x; sum = 0; while (p != NULL) { sum = sum + (*p).value; p = (*p).next; } return sum; } x valu e nex t y nex t valu e z valu e nex t p NULL x valu e nex t y nex t valu e z valu e nex t p NULL Bez uwzględniania przepływu x valu e nex t y nex t valu e z valu e nex t p NULL - Czemu p wskazuje również na NULL? Funkcje x1 = &a y1 = &b swap(x1, y1) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; } x2 = &a y2 = &b swap(x2, y2) Funkcje ● Uwzględnianie kontekstu ○ Traktujemy każde wywołanie funkcji oddzielnie tak jak to faktycznie dzieje się w programie ○ problem: Co z funkcjami rekurencyjnymi? ■ niestety trzeba szacować ● Bez uwzględniania kontekstu ○ Łączymy informacje z różnych miejsc wywołańdanej funkcji ○ na skutek czego tak naprawdę, problem sprowadzony zostaje do rozwiązania zalęzności wewnątrz danej funkcji ● Podejście uwzględniające kontekst jest oczywiście bardziej precyzyjne ale bardziej kosztowne do wyznaczenia Bez uwzględniania kontekstu x1 = &a y1 = &b swap(x1, y1) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; } x2 = &a y2 = &b swap(x2, y2) Uwzględnianie kontekstu x1 = &a y1 = &b swap(x1, y1) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; } x2 = &a y2 = &b swap(x2, y2) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; } Funkcje ● Założenie: brak parametrów funkcji ○ Oznacza to, że znamy wszystkie wywołania, bo inaczej: ● Konieczne jest wykonanie przypisania parametrów aktualny i parametrów formalnych w każdym miejscu wywołania ○ Używamy tych samych zmiennych dla wszystkich nazw parametrów formalnych w poszczególnych miejcach wywołania ● Każde wywołanie powoduje wygenerowanie nowego zbioru powiązań do parametrów formalnych Przykład x1 = &a y1 = &b p1 = x1 p2 = y1 t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; x2 = &a y2 = &b p1 = x2 p2 = y2 Alokacja pamięci (sterta) ● Najprostsze rozwiązanie: stosowanie jednego węzła w grafie do reprezentowania wszystkich komórek pamięci sterty ● Bardziej złożone rozwiązanie: dla każdego malloc w programie uzyć innego węzła ● Jeszcze bardziej wyszukane rozwiązanie: analiza kształtu ○ cel: synteza potencjalnie nieskończonych struktur, ○ ale zachowane jest wystarczająco dużo informacji, dzięki czemu możemy rozróżnić wskaźniki ze stosu do sterty, o ile to w ogóle możliwe Podsumowanie Mniej dokładne metody Metody dokładniejsze Oparte na jednoznaczności Oparta na podzbiorach bez uwzględniania przepływu uwzględnianie przepływu bez uwzględniania kontekstu uzględnianie kontekstu Brak jednoznacznej odpowiedzi, której metody użyć.