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 x pt r 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) if (p) x = &y else x := &z.. x = &y Na co wskazuje teraz x? p 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 w x := &z ptr := &x Andersen y := &w ptr := &y pt r x, y z,w Steensgard
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 x valu e nex t p nex t p y valu e y z valu e nex t valu e z nex t valu e nex t NULL nex t NULL
Bez uwzględniania przepływu x valu e nex t y valu e nex t z p valu e nex t NULL - Czemu p wskazuje również na NULL?
Funkcje x1 = &a y1 = &b swap(x1, y1) x2 = &a y2 = &b swap(x2, y2) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; }
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) x2 = &a y2 = &b swap(x2, y2) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; }
Uwzględnianie kontekstu x1 = &a y1 = &b swap(x1, y1) x2 = &a y2 = &b swap(x2, y2) swap (p1, p2) { t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1; } 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 x2 = &a y2 = &b p1 = x2 p2 = y2 t1 = *p1; t2 = *p2; *p1 = t2; *p2 = t1;
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 Oparte na jednoznaczności Metody dokładniejsze Oparta na podzbiorach bez uwzględniania przepływu bez uwzględniania kontekstu uwzględnianie przepływu uzględnianie kontekstu Brak jednoznacznej odpowiedzi, której metody użyć.