INFO517 : Programmation C
Cours du semestre 5 de la licence STIC INFO.
Responsable pour 2008--2009: Lionel Vaux.
Pensez à consulter les indications pour compiler un petit programme sur une machine des salles de TP.
N'hésitez pas à contribuer au wiki, et en particulier à cette page: clarifications, compléments, exemples… Si vous n'avez pas compris un point particulier, vous pouvez signaler votre problème sur la page de discussion (onglet en haut de cette page) ou par les moyens habituels. Il sera ensuite très positif de revenir sur cette page et de consigner ce qui vous posait problème et ce qui vous a permis de mieux comprendre.
Fonctionnement
Cet enseignement comprendra 10 séances de cours/TD (1h30) et 3 séances de TP (4h).
La distinction entre cours et TD restera floue. Je vous demanderai généralement d'écrire quelques petits programmes d'une semaine sur l'autre. Autant que possible, envoyez-moi vos fichiers sources à l'adresse lionel.vaux@univ-savoie.fr, afin que je puisse évaluer le niveau de chacun et ajuster le contenu des séances suivantes.
Et dites-moi si ça ne va pas, ou je risque d'avancer trop vite.
Objectifs du cours
- Principes généraux et particularités du langage (programmation impérative, typage fort, adressage mémoire)
- Syntaxe
- Bibliothèque standard (pour les entrées-sorties et l'interaction avec le système d'exploitation)
- Gestion de la mémoire
- Bonnes pratiques
- Outils et concepts:
- automatisation de la compilation (make),
- analyse de l'exécution et déboguage (gdb, valgrind),
- documentation (doxygen),
- boîte à outils graphique (gtk+)
Séances
Cours/TD 1 : lundi 22 septembre 2008
Présentation tout-en-un.
Le but de ce cours est de fournir le minimum vital aux étudiants pour:
- écrire un programme simple et court utilisant les types de base
- le compiler et l'exécuter
- trouver de la documentation
Après cette première séance, les étudiants devraient être capable de s'amuser un peu avec le langage.
Les exemples vus en cours
bateau.c <source lang="c">
- include <stdio.h>
int main () { /* Écrit une chaîne */ puts("Bateau !");
/* Renvoie la valeur de sortie en cas de succès */ return 0; } </source>
euros-francs-v1.c <source lang="c">
- include<stdio.h>
/* Écrit une table de conversion euros/francs
* pour euros = 0, 5, 10, ..., 100 : * version initiale */
main() {
int euros, euros_max, pas ; float francs, un_euro ;
un_euro = 6.55957 ; /* taux de conversion */
pas = 5 ; /* pas d'itération */ euros = 0 ; /* valeur initiale */ euros_max = 100 ; /* valeur maximale */ while (euros <= euros_max) { francs = un_euro * euros ; printf("%d\t%f\n", euros, francs) ; euros = euros + pas ; }
} </source>
euros-francs-v2.c <source lang="c">
- include<stdio.h>
/* Écrit une table de conversion euros/francs
* pour euros = 0, 5, 10, ..., 100 : * correction de l'alignement */
main() {
int euros, euros_max, pas ; float francs, un_euro ;
un_euro = 6.55957 ; /* taux de conversion */
pas = 5 ; /* pas d'itération */ euros = 0 ; /* valeur initiale */ euros_max = 100 ; /* valeur maximale */ while (euros <= euros_max) { francs = un_euro * euros ; printf("%3d\t%6.2f\n", euros, francs) ; euros = euros + pas ; }
} </source>
euros-francs-v3.c <source lang="c">
- include<stdio.h>
/* Écrit une table de conversion euros/francs
* pour euros = 0, 5, 10, ..., 100 : * avec un `for' */
main() {
int euros ; for (euros = 0 ; euros <= 100 ; euros = euros + 5) printf("%3d\t%6.2f\n", euros, 6.55957*euros) ;
} </source>
euros-francs-v4.c <source lang="c">
- include<stdio.h>
- define UN_EURO 6.55957 /* un euro en francs */
/* Écrit une table de conversion euros/francs
* pour euros = 0, 5, 10, ..., 100 : * définition pour le préprocesseur */
main() {
int euros ; for (euros = 0 ; euros <= 100 ; euros = euros + 5) printf("%3d\t%6.2f\n", euros, UN_EURO*euros) ;
} </source>
arrondi.c <source lang="c">
- include<stdio.h>
- define INCR 0.00001 /* incrément pour le test de précision */
- define NUM 100000 /* nombre de pas */
/* Calcule INCR*NUM en ajoutant NUM fois INCR à 0 */ main() {
float accu ; int i ; accu = 0 ; for (i=0 ; i < NUM ; i=i+1) accu = accu + INCR ; printf("%f=%f?\n",NUM*INCR,accu) ;
} </source>
arrondi-double.c (cette variante a été signalée pendant les rappels de la deuxième séance) <source lang="c">
- include<stdio.h>
- define INCR 0.00001 /* incrément pour le test de précision */
- define NUM 100000 /* nombre de pas */
/* Calcule INCR*NUM en ajoutant NUM fois INCR à 0.
* Cette version utilise un accumulateur de type `double' * pour limiter les erreurs d'arrondi */
main() {
double accu ; // On calcule en double précision int i ; accu = 0 ; for (i=0 ; i < NUM ; i=i+1) accu = accu + INCR ; printf("%f=%f?\n",NUM*INCR,accu) ;
} </source>
copie-v1.c <source lang="c">
- include<stdio.h>
/* Copie l'entrée standard sur la sortie standard */ main() {
int c;
c = getchar(); while (c != EOF) { putchar(c); c = getchar(); }
} </source>
copie-v2.c <source lang="c">
- include<stdio.h>
/* Copie l'entrée standard sur la sortie standard :
* assignation comme valeur */
main() {
int c;
while ((c = getchar()) != EOF) { putchar(c); }
} </source>
Exercices pour le 29 septembre
- Au choix:
- Sur la machine et le système de votre choix, écrire et compiler un programme C (par exemple bateau.c), puis envoyer le fichier source et le binaire obtenu à l'adresse lionel.vaux@univ-savoie.fr.
- Ne pas y parvenir et alors me contacter au plus tôt pour y remédier. Ensuite revenir au choix 1, évidemment.
- Modifier l'un des fichiers euros-francs-v?.c pour afficher une ligne d'en-tête alignée sur les résultats (et quelques fioritures). C'est-à-dire que la sortie doit ressembler à:
Euros: Francs: 0 -> 0.00 5 -> 32.80 10 -> 65.60 15 -> 98.39 20 -> 131.19 ...
- Écrire un programme francs-euros.c qui affiche une table de conversion dans le sens contraire (les comptes ronds sont en francs).
- Écrire un programme qui affiche la valeur entière, de type int, de EOF (vérifier qu'elle n'est pas dans l'intervalle entier [0..255]).
- Écrire un programme qui affiche la valeur entière du « caractère » € (il est possible que vous ne compreniez pas très bien ce qui vous arrive: on en parlera).
- Écrire un programme qui compte le nombre de caractères (au sens de getchar()) dans un fichier.
Solutions possibles pour les exercices
Pour les variations sur euros-francs-v?.c, voilà un programme qui rassemble un peu tout:
conversion.c <source lang="c">
- include<stdio.h>
- define UN_EURO 6.55957 /* un euro en francs */
/* Écrit une table de conversion euros/francs
* pour euros = 0, 5, 10, ..., 100 : * définition pour le préprocesseur */
main() {
int euros ; int francs ; printf("Euros:\t\tFrancs:\n") ; for (euros = 0 ; euros <= 100 ; euros = euros + 5) printf(" %3d\t\t %6.2f\n", euros, UN_EURO*euros) ;
printf("\nFrancs:\t\tEuros:\n") ; for (francs = 0 ; francs <= 100 ; francs = francs + 5) printf(" %3d\t\t %6.2f\n", francs, francs/UN_EURO) ;
} </source>
Un programme qui affiche la valeur entière (type int) de EOF:
<source lang="c">
- include<stdio.h>
/* Écrit la valeur entière de EOF */ main() { printf("%d\n",EOF) ; } </source> On sauve ça dans EOF.c, puis on compile avec
$ gcc -Wall -o EOF EOF.c
Les erreurs produites sont standard (mauvais prototype pour main). L'exécution sur ma machine donne:
$ ./EOF -1
La valeur entière de €, sur le même modèle:
<source lang="c">
- include <stdio.h>
main () { int euro = '€' ;
printf("%d\n",euro) ; } </source> On sauve ça dans euro.c, puis on compile avec:
$ gcc -Wall -o euro euro.c
ce qui produit les avertissements:
euro.c:3: attention : return type defaults to «int» euro.c:4:13: attention : constante caractère multi-caractères euro.c: Dans la fonction «main» : euro.c:7: attention : control reaches end of non-void function
La deuxième ligne est le premier signe que quelque chose de bizarre est à l'œuvre. À l'exécution, on obtient:
$ ./euro 14844588
Savez-vous expliquer ce qui se passe ?
Pour compter le nombre de caractères: ce qu'on a vu en cours fait mieux.
Cours/TD 2 : lundi 29 septembre 2008
Rappels et précisions; fonctions; tableaux.
Les exemples vus en cours
puissance-v1.c <source lang="c">
- include<stdio.h>
- define MAXEXP 16
/* Fonction puissance(a: int, b: int) : int
* renvoie a à la puissance b, si b positif, 1 sinon */
int puissance (int a, int b) { int i, ret ; ret = 1 ; for (i=0; i<b; ++i) /* ++i incrémente i */ ret = a * ret ; return ret ; }
/* Procédure affiche(a: int, b: int)
* formatte le résultat de puissance(a,b) */
void affiche(int a, int b) { printf("%2d^%2d = %10d\n",a,b,puissance(a,b)) ; }
/* Fonction principale: affiche les puissances de 2, de 1 à 2^MAXEXP */ void main () { int i ; for (i=0; i<=MAXEXP; ++i) affiche(2,i) ; } </source>
puissance-v2.c
<source lang="c">
- include<stdio.h>
- define MAXEXP 16
/* Prototype de la fonction puissance(a: int, b: int) : int
*/
int puissance (int a, int b) ;
/* Procédure affiche(a: int, b: int)
* formatte le résultat de puissance(a,b) */
void affiche(int a, int b) { printf("%2d^%2d = %10d\n",a,b,puissance(a,b)) ; }
/* Fonction principale: affiche les puissances de 2, de 1 à 2^MAXEXP */ void main () { int i ; for (i=0; i<=MAXEXP; ++i) affiche(2,i) ; }
/* Code de la fonction puissance :
* puissance(a,b) renvoie a à la puissance b, si b positif, 1 sinon */
int puissance (int a, int b) { int ret ; ret = 1 ; /* On utilise l'argument b comme une variable locale */ for (/* pas d'initialisation nécessaire */; b>0; --b) ret = a * ret ; return ret ; } </source>
fibonacci.c
<source lang="c">
- include<stdio.h>
- define MAX 30
/* calcule u[n], où u[0]=u[1]=1 et u[n+2]=u[n+1]+u[n] */ int fibo (int n, int rec) { if (n<=1) return 1 ; else return (fibo(n-1,rec+1)+fibo(n-2,rec+1)) ; }
/* affiche fibo(n) pour n de 0 à MAX */ int main () { int i ; for (i=0;i<=MAX;++i) printf("fibo(%2d)=%8d\n",i,fibo(i,0)) ; return 0 ; } </source>
tracerec.c
<source lang="c">
- include<stdio.h>
- define N 15
/* Procédure blanks(n: int)
* insère n espaces. */
void blanks (int n) {
for(;n>0;--n) putchar(' ') ;
}
/* Calcule fibo(n) (voir fibo.c) et affiche le résultat.
* Cet affichage dans la fonction permet de tracer le * flot de récursion (l'ordre dans lequel les appels se font). * * On représente la profondeur de récursion par l'indentation, * au moyen du second argument : tous les affichages sont décalés * de rec espaces. L'entier rec est incrémenté dans l'appel récursif, * et on démarre avec rec=0 dans main() * */
int fibo_rec (int n, int rec) {
blanks(rec) ; printf("fibo(%2d)=?\n",n) ; if (n<=1) { blanks(rec) ; printf("fibo(%2d)=1\n",n) ; return 1 ; } else {
/* On peut déclarer des variables locales à un bloc */
int ret=fibo_rec(n-1,rec+1)+fibo_rec(n-2,rec+1) ; blanks(rec) ; printf("fibo(%2d)=%d\n",n,ret) ;
return ret ;
}
}
/* Calcule fibo(N) avec affichage des étapes de récursion */
int main () {
fibo_rec(N,0) ; return 0 ;
} </source>
segfault.c
<source lang="c">
/* CE CODE EST VOLONTAIREMENT FAUTIF ! */
/* Notre première erreur de segmentation:
* les appels récursifs s'empilent sans limite et on sort de la mémoire * disponible. */
- include<stdio.h>
- define MAX 30
/* calcule u[n], où u[0]=u[1]=1 et u[n+2]=u[n+1]+u[n] */ int fibo (int n, int rec) { return (fibo(n-1,rec+1)+fibo(n-2,rec+1)) ; }
/* affiche fibo(n) pour n de 0 à MAX */ int main () { int i ; for (i=0;i<=MAX;++i) printf("fibo(%2d)=%8d\n",i,fibo(i,0)) ; return 0 ; } </source>
textstat.c
<source lang="c">
- include<stdio.h>
/* En théorie, les valeurs entières des caractères
* sont dans un intervalle non précisé par la norme. * * Dans cet exemple, on considère que les caractères sont des entiers sur 8 * bits non signés (cf. CHAR_MIN, CHAR_MAX). * * Ceci était passé sous silence lors du cours. * */
- define CHAR_MIN 0
- define CHAR_MAX 255
/* CHAR_NUM = CHAR_MAX - CHAR_MIN + 1 */
- define CHAR_NUM 256
/* Une macro évidente */ void aff (char c, int n) { printf("%c:%2d ",c,n) ; }
/* Compte le nombre d'occurrences de chaque caractère
* et affiche certains résultats */
int main() { int c, i ; int nb [CHAR_NUM] ;
/* Mise à zéro */ for (i=0;i<CHAR_NUM;++i) nb[i]=0 ;
/* Calcul */ while((c=getchar()) != EOF) ++nb[CHAR_MIN+c] ;
/* Affichage */
/* Les lettres minuscules sont consécutives de 'a' à 'z'. */
printf("Minuscules:\n") ; for (c='a'; c<='z' ; ++c) aff(c,nb[CHAR_MIN+c]) ;
/* De même pour les majuscules de 'A' à 'Z'. */
printf("\nMajuscules:\n") ; for (c='A'; c<='Z' ; ++c) aff(c,nb[CHAR_MIN+c]) ;
/* Et les chiffres de '0' à '9'. */
printf("\nChiffres:\n") ; for (c='0'; c<='9' ; ++c) aff(c,nb[CHAR_MIN+c]) ;
printf("\nSauts de lignes: %d\n",nb[CHAR_MIN+'\n']) ;
return 0 ; }
</source>
Exercices pour le 6 octobre
Sur les fonctions
- Réécrire le programme conversion.c en utilisant une fonction pour l'affichage. Rendre ce programme plus modulaire en écrivant une procédure avec le prototype void francs_euros (int min, int max, int pas) qui afffiche la conversion de n francs en euros pour n variant de min à max par pas de pas et une procédure void euros_francs (int min, int max, int pas) qui affiche la même chose mais dans l'autre sens de conversion.
- Écrire une fonction int fact (int n) qui calcule la factorielle de n de deux manières différentes: une par récursion, une en utilisant une boucle. Essayer ces fonctions sur des valeurs pas trop petites et détecter quand ça dégénère.
Sur les tableaux et les chaînes
- Écrire une procédure void aff (int n) qui affiche les n premiers éléments du tableau t (variable externe de type int t[]).
- Écrire une procédure void aff_inv (int n) qui affiche les n premiers éléments du tableau t dans l'ordre inverse (cases n-1 à 0).
- On donne le programme:
<source lang="c">
- include <stdio.h>
void incr (int n) { ++n ; }
void incr_t0 (int t[]) { ++t[0] ; }
int main () { int n = 0 ; int t[1] = { 0 } ; incr(n) ; incr_t0(t) ; printf("n: %d\n",n) ; printf("t[0]: %d\n",t[0]) ; return 0 ; } </source> Compiler et exécuter ce programme. Que remarque-t-on ? Qu'en déduire sur les tableaux comme arguments de fonctions.
- Que fait le programme fin_chaine.c:
<source lang="c">
- include <stdio.h>
int main () { printf("Bateau.\n\0Et puis aussi...") ; return 0 ; } </source> Expliquer.
- Écrire un programme qui affiche les lignes données en entrée seulement si elles comportent plus de 20 caractères.
- Écrire une fonction void pal(char s[]) qui renvoie 1 si la chaîne s est un palindrome et 0 sinon.
- Écrire une procédure void renverse(char s[]) qui renverse la chaîne s.
Références
- The C programming language, de Kernighan et Ritchie;
- Le langage C, version française du précédent;
- Le polycopié de Bernard Cassagne, disponible ici, au format html (consultable en ligne) ou pdf;
- Le wikilivre Programmation C: un livre de cours sur le mode wikipedia.