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ć.