int - WebCampus

Transcription

int - WebCampus
'
$
Méthodes de programmation
Wim Vanhoof
&
%
1
'
$
Informations pratiques :
• Coordonnées :
W. Vanhoof
Faculté d’informatique (Local : 417)
Tél. : 081/72.49.77
Email : [email protected]
• Organisation :
— cours théoriques (30 heures – 8 séances)
— travaux pratiques (30 heures – 8 séances)
• Support (WebCampus)
— syllabus
— transparents
&
%
2
'
$
Objectifs du cours
• Objectif principal :
Apprendre une démarche systématique et igoureuse de construction de programmes à
priori corrects
• Objectifs conséquents :
• Apprendre des éléments d’analyse et de raisonnement sur des programmes
• Apprendre des techniques méthodologiques de construction de programmes, y
inclus les structures de données
• Introduction de certains concepts et techniques-clés de l’algorithmique
&
%
3
'
$
Contenu du cours (provisoire)
• Introduction : notions élémentaires (invariant et complexité)
• Spécification
— spécification de programmes
— spécification de structures de données
• Preuve de correction
• Construction de programmes
— induction sur la structure des calculs
— induction sur la structure des données
• Techniques algorithmiques :
— techniques de recherche et de tri
— greedy, diviser pour régner
&
%
4
'
$
Evaluation
• examen écrit
— exercices de raisonnement (type preuve de correction, calcul de complexité,. . .)
— exercices de programmation (utilisant une méthode particulière)
• travail individuel
— deux parties, énoncés distribués en TP
— objectif : faire un exercice de plus grande taille
— pas obligatoire, mais permet d’obtenir un point bonus à l’examen
&
%
5
'
$
Prérequis du cours
• Maı̂trise des techniques de base de la programmation impérative et les structures de
données statiques (tableaux)
• Maı̂trise de la création et manipulation de simples structures de données dynamiques
(listes enchaı̂nées)
• Les types de données abstraits de base et leur implémentation (Stack, Queue, Heap,
Priority Queue)
• Au niveau mathématique :
— théorie des ensembles (et les notations courantes)
— la logique du premier ordre (et les notations courantes)
Le cours utilise le langage C, mais les concepts et techniques vus se transposent facilement
vers d’autres langages (voir même paradigmes).
&
%
6
'
$
Références
• A. Aho et J. Ullman Foundations of Computer Science W.H. Freeman & Company,
1992.
• K.R. Apt et E.R. Olderog. Verification of Sequential and Concurrent Programs.
Springer-Verlag 1997.
• R.C. BackHouse. Program Construction and Verification. Prentice-Hall, International
series in computer science, 1986.
• E.W. Dijkstra. A discipline of programming. Prentice-Hall, 1976.
• D. Gries, The Science of Programming., Springer-Verlag 1981. Chapters 13 – 16, pp.
163 –215.
• J. Julliand, Cours et exercices corrigés d’algorithmique - vérifier, tester et concevoir des
programmes en les modélisant, Vuibert 2010.
• W.A. Wulf, M. Shaw, L. Flon, and P. Hilfinger, Fundamental Structures of Computer
Science.. Addison-Wesley, 1981. Chapter 5, pp. 101 –143.
• J. Kleinberg and E. Tardos. Algorithm Design, Pearson Intl. Edition, 2006
&
7
%
'
$
Introduction : notion élémentaires
&
%
8
'
$
La programmation en échec : quelques bugs infâmes
• Premier vol de l’Ariane V
• Mars Climate Orbiter
• Appareil de radiothérapie Therac-25
• Missile sol-air Patriot
• ...
&
%
9
'
$
L’échec d’un programme
• Le programme peut ne pas être correct
• Le programme peut ne pas être assez efficace
• Le programme peut ne pas être assez sécurisé
&
%
10
'
$
Exemple d’un fragment de programme pas correct
if (likely(state == 0)) {
if (unlikely(t == 0)) {
while (unlikely(*ip == 0)) {
t += 255;
ip++;
NEED_IP(1);
}
t += 15 + *ip++;
}
t += 3;
Code qui se trouvait pendant 20 ans dans l’algorithme de compression LZ présent dans le
kernel Linux.
&
%
11
'
$
Exemple d’un fragment de programme pas efficace
void replace(char* text, char oldchar, char newchar) {
for (int i = 0; i < strlen(text); ++i) {
if (text[i] == oldchar)
text[i] = newchar;
}
}
Il n’y a pas de vraie erreur, mais regardons quelques temps d’exécution
&
%
12
'
$
taille de *text
replace
replace_a
1.000 octets
0.000069 secondes
0.000007 secondes
10.000
0.004416
0.000036
100.000
0.497699
0.000352
1.000.000
49.174263
0.003730
&
%
13
'
$
Solution linéaire :
void replace_a(char* text, char oldchar, char newchar) {
long l = strlen(text);
for (int i = 0; i < l; ++i) {
if (text[i] == oldchar)
text[i] = newchar;
}
}
&
%
14
'
$
Exemple d’un fragment de programme qui présente un risque de sécurité
HRESULT GetMachineName(
WCHAR *pwszPath,
WCHAR wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1])
{
pwszServerName = wszMachineName;
LPWSTR
pwszTemp = pwszPath + 2;
while (*pwszTemp != L ' \\ ' )
*pwszServerName++ = *pwszTemp++;
...
}
Code qui était présent dans une implémentation du mécanisme RPC. La vulnérabilité a été
exploitée par le virus W32.Blaster.Worm en 2003.
&
%
15
'
$
Observation
On a besoin de mécanismes qui permettent de formaliser le raisonnement sur la correction
et l’efficacité du code
Dans la suite on va introduire quelques notions-clés qui sont à la base de ces raisonnements
&
%
16
'
$
Quelques définitions
• On appelle assertion une affirmation ; une proposition que l’on soutient comme vraie.
• Deux assertions particulières concernant les boucles :
(a) L’invariant (le plus fort)
(b) La condition d’arrêt
&
%
17
'
$
int shuffle_left(int a[N]) {
int l,r,k;
l = 0;
k = 0;
while (l < N - k) {
if (a[l] != 0) {
l++;
} else {
k++;
for (r=l+1; r<=N-k; r++)
a[r-1] = a[r];
a[N-k] = 0;
}
}
return k;
}
&
%
18
'
$
La complexité algorithmique
• Mesurer la consommation de ressources (temps et espace mémoire) d’un programme
• T (n) étant une fonction N 7→ R représentant le nombre d’instructions élémentaires
exécutées en fonction d’une entrée de taille n ∈ N.
• E(n) étant une fonction N 7→ R représentant la consommation mémoire en fonction
d’une entrée de taille n ∈ N.
• Souvent on fait la distinction entre :
— le meilleur des cas
— le pire des cas
• Exemple : shuffle_left
&
%
19
'
$
Observations
• Cette mesure d’efficacité, indépendante des machines et des données permettent de
comparer l’efficacité de différents algorithmes.
• Etant une fonction, elle représente la façon dont le temps d’exécution augmente
quand la taille des entrées augmente
• D’où on fait typiquement abstraction des constantes et on ne regarde que l’ordre de
grandeur :
— p.ex. 3N
+ 2 is Θ(N )
&
%
20
'
$
A propos d’ordres (notation ”Grand O”)
Soit T (n)
: N 7→ R+
• T (n) ∈ O(f (n)) ⇔ T (n) est asymptotiquement bornée, par le dessus, par
f (n) (à un facteur c près) : ∃k ∈ N : n ≥ k ⇒ T (n) ≤ cf (n)
• T (n) ∈ Ω(f (n)) ⇔ T (n) est asymptotiquement bornée, par le dessous, par
f (n) (à un facteur c près) : ∃k ∈ N : n ≥ k ⇒ T (n) ≥ cf (n)
• T (n) ∈ Θ(f (n)) ⇔ T (n) ∈ O(f (n)) et T (n) ∈ Ω(f (n))
On utilise ces concepts pour représenter la complexité (en temps ou en espace) d’un
algorithme.
&
%
21
'
$
Les complexités les plus rencontrées
O(1)
constant
O(log2 (n))
logarithmique
O(n)
linéaire
O(n log2 (n))
n log2 (n)
O(n2 )
quadratique
O(n3 )
cubique
O(2n )
exponentiel
A savoir : on utilise parfois O(f (n)) à la place de Θ(f (n)) (abus de notation !)
&
%
22
'
$
&
%
23
'
$
Exemple : deux algorithmes alternatifs pour déplacer les zéros vers la fin d’un tableau :
— copy_over
— converging_pointers
Faites attention au changement de complexité par rapport à shuffle_left !
— dans le meilleur des cas
— dans le pire des cas
&
%
24
'
$
int copy_over(int a[N]) {
int b[N];
int i,j,k;
i = 0;
j = 0;
k = 0;
while (i < N)
if (a[i] != 0) {
b[j] = a[i];
i++;
j++;
} else {
i++;
b[N-k-1] = 0;
k++;
};
&
%
25
'
$
for(i=0;i<N;i++)
a[i] = b[i];
return k;
}
&
%
26
'
$
int converging_pointers(int a[N]) {
int l, k;
l = 0;
k = 0;
while (l <= N-k-1) {
if (a[l] != 0) {
l++;
} else {
a[l] = a[N-k-1];
a[N-k-1] = 0;
k++;
}
}
return k;
}
&
%
27
'
$
Les complexités des trois algorithmes :
Algorithme
T (N )
E(N )
shuffle_left
O(N 2 )
O(1)
copy_over
O(N )
O(N )
converging_pointers
O(N )
O(1)
&
%
28
'
$
En guise de conclusion : le facteur constant et l’effet quadratique
int shuffle_left_slow(int a[N]) {
int l,r,k;
l = 0;
k = 0;
while (l < N - k) {
if (a[l] != 0) {
l++;
} else {
k++;
for (r=l+1; r<=N-1; r++)
a[r-1] = a[r];
a[N-1] = 0;
}
}
return k;
}
&
%
29
'
$
Quelques vrais temps d’exécution :
Taille des entrées
10.000
100.000
1.000.000
shuffle_left
0.01
1.25
126.71
shuffle_left_slow
0.02
1.46
149.11
converging_pointers
0.00
0.00
0.01
&
%
30

Documents pareils