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