Most good programmers do their work not because they expect to get paid or get recognition, but because they enjoy the programming.
--Linus Torvalds
Cette page s'adresse à l'apprenti programmeur en langage C. Elle donne quelques informations sur la pratique de la C-programmation dans le but de réaliser des GCC-expériences numériques sous un système GNU/linux. Il ne s'agit ni d'un manuel ni d'un cours de programmation. En effet, le fameux manuel The C programming language des fondateurs : Dennis Ritchie, Brian Kernighan, (Ken Thompson) est facile à trouver sur la toile. Pour ma part, j'ai découvert le C avec le manuel de Garetta, et je vous recommande vivement la visite de son site. Concernant gcc, il y a la précieuse introduction de Brian Gough préfacée par Stallman. Vous pouvez parcourir les cours de Bernard Cassagne ou encore celui d'Anne Canteaut. qui me paraissent complets et bien structurés, enfin je vous invite à participer au groupe de discussions fr.comp.langage.c , parcourir la faq du group comp.lang.c, ou encore suivre les conseils de Emmanuel Delahaye.
First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.[skip ]
-- George Carrette
La dernière version de votre un programme, écrit en langage C, vient tout juste de passer le cap de la compilation. Notre collègue commun gcc, ne vous signale aucune erreur et pourtant, votre exécutable ne se termine pas comme il devrait. Si son dernier message segmentation fault ou encore bus error vous laisse perplexe, vous trouverez dans ces frames quelques moyens pour vous en sortir.
Bien entendu, ce petit texte s'adresse aussi à ceux et celles qui écrivent des programmes qui ne finissent jamais, ou font ce qu'ils ne devraient pas etc : les apprentis programmeurs, futurs mangeurs de pizzas et autres insecticides.
C is not a high-level language.[skip ]
--Brian Kernighan.
Le langage C est un langage de programmation de bas niveau, une sorte d'assembleur amélioré. L'apprentissage de ce type de langage ne peut se faire sans penser avec adresse et valeur.
// endianess test int main( void ) { char * x; int z; z = 1 + (2 << 8) + (3<<16) + (4<<24); x = (char*) &z; printf("%p %d\n", x, *x); x++; printf("%p %d\n", x, *x); x++; printf("%p %d\n", x, *x); x++; printf("%p %d\n", x, *x); return 0; }Le programme ci-dessus fait un test de boutisme : ordre des octets en mémoire.
$ ./a.out 0x7fff90b629f4 1 0x7fff90b629f5 2 0x7fff90b629f6 3 0x7fff90b629f7 4Le débutant devrait sauter la suite.
int main( void ) { char * x; long long z; z = 683 * 8011; z *= 29*29; z *= 13; z <<= 3; x = (char*) &z; printf("my %s world\n", x); return 0; }
$ gcc z.c z.c: In function ‘main’: z.c:12: attention : incompatible implicit declaration of built-in function ‘printf’ $ ./a.out my hello world
#includeint main( int argc, char*argv[], char*env[] ) { long long z; int i=0, j=0; while ( *env[i] != 85) i++; while ( env[i][j] != 61) j++; j++; z = 2*j*2*j+2*j+1; z = (z << 32) + 1819043176; printf("%s %s!\n", &z, & (env[i][j]) ); return 0; }
Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris.[skip ]
-- Larry Wall
[0] Programmez ce que vous comprenez.
[1] Ecrivez des programmes propres, clairs et concis.
[2] Les bons types font les bons programmes.
[3] Les identificateurs longs pour les variables globales
[4] Courts pour les locales, mais toujours un identificateur de type explicite.
[5] Evitez les variables globales
[6] Décomposez votre code en petites procédures et fonctions
[7] Commentez votre source sans être verbeux.
[8] Indentez votre texte.
[9] Certifiez vos procédures par des tests solides
[A] En matière de programmation : presque == pas du tout.
[B] Vérifiez régulièrement (man) votre compréhension des fonctions standards
[C] Méfiez vous des copier/coller
[D] Méfiez vous des copier/coller (bis)
[E] Ne programmez pas après deux heures du matin.
[F] Programmer souvent.
[G] erreur de segmentation !
Nous évoquerons une assez petite partie de moins de 5% des possiblités du compilateur gcc[skip ] Le débutant devrait compiler ses projets avec les options -Wall et -g, comme un expert :-) :
--PL
gcc -Wall -g prog.cLes principales options de compilations utilisées dans ce document :
-Wall : obtenir les avertissements courants. -g : obtenir du code debugable, en liason avec gdb, valgrind -D : passer une macro -U : -E : passer la source au préprocesseur -S : obtenir un programme en assembleur -c : obtenir un code objet -o : nom de l'output, par défaut : a.out -l : librairie à utiliser pour une reliure -I : chemin vers des fichiers d'entêtes -L : chemin vers des bibliothèques non-standard -shared : pour construire des bibliothèques dynamiques --help : obtenir de l'aide : optimizers, warnings... -O2 : optimiser le code -pg : profilage en liason avec gprof -fprofile-arcs -ftest-coverage : test de couverture avec gcov
It is easier to port a shell than a shell script.[skip ]
-- Larry Wall
echo : envoyer un message sur stdout : echo $PATH : echo -e '1\n2\n3\n' > /tmp/foo date : obtenir la date at : exécution différée : at now + 1O minutes echo "il est l'or" mkdir : créer un répertoire : mkdir -p /tmp/phil/src cd : changement de répertoire pwd : identifier le répertoire courant rm : effacer un fichier. : rm -f *.o mv : déplacer un fichier rename : renommer des fichiers : rename .txt .old *.txt cp : copier un fichier, un répertoire : cp -ri /tmp/src /tmp/dst man : voir le manuel, the command ! : man man : man 3 printf uname : information sur le système lscpu : information sur l'architecture xterm : un terminal X de plus gedit : éditer un fichier, idéal pour débuter : le plus souvent lancé en tache de fond : gedit prog.c & : terminal + gedit + vi : basta cosi ! bg : lancer un processus en tache de fond : prog.exe ctrl-z. : bg emacs : éditer un fichier, l'éditeur préferé des mangeurs : de pizzas atomiques, à essayer donc. : strace -o /tmp/emacs.txt -e trace=open emacs : grep -c open /tmp/emacs.txt : 447 ( houf ! ) vi : The number of the beast : vi vi vi. : éditeur des mangeurs de pizzas napolitaines ! : strace -o /tmp/vi.txt -e trace=open vi : grep -c open /tmp/vi.txt : 22 ( raisonable ? ) kyle : beurck ?! indent : mettre en forme une source C : indent -kr prog.c history : historique des commandes : !! lpr : imprime in fichier : lpr source.c a2ps : idem : a2ps *.c cat : afficher un fichier : cat /proc/cpuinfo xclip : lire, écrire dans le clipboard X11 : xclip -o | sed 's/<[^<>]*>//g' more : less : visiter un fichier. : ./prog.exe | more : / recherche un motif ls : lister des fichiers, les repertoires... : ls -tild * wc : compter les caractères, les lignes... : wc -l cpu /proc/cpu grep : trouver un motif dans un fichier : grep -ir pi --include=*.h /usr : grep -c proc /proc/cpuinfo sort : trier les lignes d'un fichier : sort -h data.txt uniq : comparer des lignes : sort data.txt | uniq -c | sort -d find : trouver un fichier : find /usr -name "math.h" : find /usr -name "math.h" --exec grep -i pi {} \; file : obtenir des informations sur un fichier : file pl.png pl.png: PNG image data, 32 x 22, 8-bit/color RGBA, non-interlaced convert : conversion d'images convert src.png -resize 32x32 dest.png gnuplot : faire des graphiques xfig : faire des figures dot : faire des graphes script : echo des in/output vers un fichier : script -a montravail.txt wget : telecharger des fichiers via http tar : manipuler une archive : tar cvf big.tar big : tar zxvf big.tar.gz ssh : se loguer sur une machine distante, tunnel etc... : ssh -f -N -L 2222:target.univ-tln.fr:22 michko@gate.univ-tln.fr : ssh -p 2222 michko@localhost : ssh-keygen, ssh-copy-id ... scp : copier des fichiers vers un hote distant. : scp -P 2222 foo.txt localhost: gcc : compiler une source : gcc -Wall -g prog.c -o prog.exe : gcc -O2 prog.c -o fast.texe aspell : véfier l'orthographe : aspell -d en --mode=tex -c tips.tex tex : compiler des textes : tex foo.tex ( pure teX ) : pdflatex bar.tex : bibtex bar gdb : debugguer un exécutable : gdb prog.exe : gdp [pid] pour attraper un processus en cours make : compilation automatique. ar : manipule les archives. touch : dater (créer) un fichier. cut : extraire des champs. : cut -d ':' -f1,3 /etc/passwd split : éclater un fichier xargs : argumenter avec les lignes : cat fic.txt | xargs echo -e -n1 time : (/urs/bin/time ) obtenir un rapport d'exécution, : temps de calculs et bien d'autres choses. : /usr/bin/time -a -o mesures.txt prog.exe bc : calculatrice : echo 'l(2^20) / l(10)' | bc -l; : -> 6.02059991327962390429 ps : lister les processus en cours. : ps -e -opid,cmd top : processus en cours, idéal pour voir bouillir : la marmite, la consommation mémoire. watch : lancer la commande à intervalle régulier; : watch who kill : envoyer un signal : kill -9 31415 killall : envoyer un signal : killall sshd hexdump : scruter un fichier, pour detecter les bad chars ! : hexdump -c prog.c strings : affichier les chaînes dans un binaire. gprof : profiler un exécutable. valgrind: détecter les fuites mémoires join : joindre des fichiers. paste : un cat à la verticale ! nl : numeroter les lignes. diff : comparer des fichiers sed : filtrer un fichier. : sed 's/[ ][ ]*/\t/g' fic.txt : sed 's/\([0-9]*\)[^0-9]*\([O-9]*\)/\2-\1/' awk : filtrer les colonnes des fichiers : awk -F: '/mich/ {print $1,$6}' /etc/passwd rsync : synchroniser des fichiers : rsync -az -e ssh --delete ~/cyberie/*.html ou812: git : gestion de version : git clone git@mapix.hd.free.fr unison : synchronisation bi-directionnelle unison /tmp/local /tmp/distant nohup : quand il faut calculer jusquau bout de la nuit, voir plus ! : nohup prog.exe & : ps -C prog.exe : tail nohup.out : exit disown : quand on oublie d'utiliser nohup !! : prog.exe : ctrl-z : bg ; disown ; exit objdump : extraire des informations d'un fichier objet : objdump -S a.out nm : liste les symboles d'un fichier objet ltrace : trace les appels aux librairies dynamiques : ltrace factor 123456789 strace : trace les appels systèmes. : strace wget langevin.univ-tln.fr xclip : manipulation du presse papier : aprés selection du code source d'une page html : xclip -o | grep -Eo 'href=[[:space:]]*"[^"]*"'
C is a quirky, imperfect, but it is incredibly successful language.[skip ]
-- Dennis Ritchie
typedef, struct, union, enum, auto , register, static, extern const, volatile signed, unsigned char, int, short, long double, float for, while, do, continue, break, if, else, enum, float, for, switch, case, default, goto, return,Finalement, le langage C, c'est pas grand-chose !
int x; x = sizeof x;Il ne faut pas confondre le langage C et la bibliothèque standard glibc qui contient des fonctions utiles pour le développeur. Par programmation en Langage C sous unix, j'entend : langage C, biblothèques (glibc,...) et commandes externes associées présentes sur un système linux (gcc,gdb,...)
Why do programmers always mix up Halloween and Christmas?[skip ]
A: Because Oct 31 == Dec 25!
taille en bits | min | max | |
char | 8 | -128 | 127 |
unsigned char | 8 | 0 | 255 |
short | 16 | -32768 | 32767 |
unsigned short | 16 | 0 | 65535 |
long | 32 | -2 147 483 648 | 2 147 483 647 |
unsigned long | 32 | 0 | 4 294 967 296 |
long long | 64 | ||
float | 32 (7 chiffres décimaux) | 0.29 E-38 | 1.70E+38 |
double | 64 (15 chiffres) | 0.56 E-308 | 0.90 E+308 |
long double |
printf( "%d %d %d", sizeof (char), sizeof (int), sizeof(long) );
sizeof s'applique aussi aux variables mais ce n'est pas une fonction.
int t[10]; char* p; // sizeof t --> 10 * sizeof(int) // sizeof p --> 4 :-( --> 8 :-) // sizeof *p --> 1
Les limitations sur la valeur d'une variable d'un type est source d'erreur, typiquement boucles infinies par effet de bord.
Je me rappelle de l'époque du passage à 32 bits, la plupart des gens étaient vraiment enthousiastes, le passage à 64 bits s'est presque fait en silence... Les temps changent !
Sur mon bureau, une petite machine à 300 euros :$ uname -m x86_64 $ free -m total used free shared buffers cached Mem: 1756 1731 24 0 177 453 -/+ buffers/cache: 1100 655 Swap: 3519 106 3413 $ grep -c process /proc/cpuinfo 4et dire que c'est du bas de gamme...
Avec le C, vous pouvez dépasser les bornes des limites mais c'est rarement sans incident ![skip ]
-- pl
Les limites sonts déclarées par des constantes dans le fichier entête limits.h.
$ grep MAX /usr/include/limits.h #define MB_LEN_MAX 16 # define SCHAR_MAX 127 # define UCHAR_MAX 255 # define CHAR_MAX UCHAR_MAX # define CHAR_MAX SCHAR_MAX # define SHRT_MAX 32767 # define USHRT_MAX 65535 # define INT_MIN (-INT_MAX - 1) # define INT_MAX 2147483647 # define UINT_MAX 4294967295U # define LONG_MAX 9223372036854775807L # define LONG_MAX 2147483647L # define LONG_MIN (-LONG_MAX - 1L) # define ULONG_MAX 18446744073709551615UL # define ULONG_MAX 4294967295UL # define LLONG_MAX 9223372036854775807LL # define LLONG_MIN (-LLONG_MAX - 1LL) # define ULLONG_MAX 18446744073709551615ULL LLONG_MAX, and ULLONG_MAX. Instead only the values gcc defined for # define LLONG_MIN (-LLONG_MAX-1) # ifndef LLONG_MAX # define LLONG_MAX __LONG_LONG_MAX__ # ifndef ULLONG_MAX # define ULLONG_MAX (LLONG_MAX * 2ULL + 1)
variable := type + adresse ![skip ] L'affectation permet de modifier les variables, pour affecter le contenu de l'expression exp à la variable x, j'ecris tout simplement
-- Pavle Michko
x = expL'expression doit avoir le meme type que la variable x, ou de type compatible. Par exemple, les affectations suivantes sont valides :
char car; int n; float pi; pi = 3.1415; n = pi; // ici, n vaut 3 c = n * 20 + pi; // ici, c vaut 63 c = 200 + c; // ici, c vaut 7Les flottants sont convertis en entiers (partie entière). L'affectation est un opérateur associatif à froite, et le langage C permet d'ecrire des trucs bizarres du genre :
x = y = z;
Le débutant sait rapidement
faire
la différence entre égalité == ( 2 fois = )
et affectation =, mais copier/coller et autres erreurs de
frappe conduisent à écrire des instructions
alternatives douteuses :
if ( x = y ) printf("egaux"); else printf("differents");L'echo de cette instruction est "egaux" sauf si y est nul.
warning: suggest parentheses around assignment used as truth value.
Algorithms + Data Structures = Programs[skip ] En C, on déclare un type comme une variable : il ne faut pas se géner sur l'emploi des typedef pour introduire de la sémantique dans la source !! En effet, avec un petit effort, une code C peut passer pour un algorithme.
--Niklaus Wirth
#define dimen 3 typedef double vecteur[ dimen ]; double pscalaire( vecteur x, vecteur y );Une liste chaînée de ce type ?
typedef struct { vecteur vec; struct _ll_ * next; } enrvecteur, *lstvecteur;
do c = getchar(); while ( isdigit(c) ); // oops unget(stdin, c);
The trouble with programmers is that you can never tell what a programmer is doing until it’s too late[skip ] La compilation d'un programme C donne un exécutable, nommé par défaut a.out. L'option -o du compilateur gcc permet de donner un autre nom.
-- Seymour Cray
gcc -o prog prog.cDans tous les cas sont associés un flux rentrant , un flux sortant et un flux de sortie des erreurs. Une source C référence ces flux respectivement par : stdin (standard input), stdout (standard output) et stderr (standard error). La plupart des opérations d'entrée/sortie opèrent ( ou peuvent opérer) sur ces deux flux; c'est le cas des opérations : getchar, putchar, puts, printf, gets, scanf, etc... Une bonne habitude à prendre : envoyer les messages d'erreurs sur le flux stderr:
fprintf( stderr, "warning : undefined object");Les flux standards correspondront aux fichiers standard du futur processus : 0, 1 et 2. Les puissants mécanismes de tube et redirection de fichiers du système vous permettent de combiner efficacement les entrées sorties de vos programmes.
$ prog < source # execution de prog avec le fichier source comme entrée $ prog > destin # execution de prog avec creation d'un fichier destin pour sortie $ prog >> destin # la sortie de prog est accrochée à la fin du fichier destin $ cat prog.c | prog | sort > destin # le fichier source est placé sur la sortie standard de cat # qui devient le flux rentrant de prog dont la sortie pipée # vers sort qui la trie et le resultat est envoye dans le fichier destin.Par exemple, si le programme somme ajoute tous les nombres lus sur l'entrée standard et dépose le résultat sur la sortie standard alors :
$ echo '1 2 3' | somme 6
while ( putchar( getchar() ) ) ; //loops like a poor cat ![skip ]
-- pl
int getchar( void ) int fgetc( FILE *src) int putchar( int car ) int fputc(int c, FILE *dst);Les fonctions pour lire et écrire un caractère sur les entrées/sorties standard, ou bien sur un flux. D'une manière générale, le "f" en premier signifie qu'une fonction requiert un flux : fputc, fgetc,fscanf,fgets...
unsigned char car; do car = getchar(); while ( car != EOF );Plus rarement, il est utile de remettre un caractère dans le flux :
int ungetc(int c, FILE *src);
int car; do car = getchar(); while ( ! isdigit(car) ); //oups !!! ungetc( car, stdin ); scanf("%d", &z);Et des fonctions lire et écrire des chaines
char * fgets(char *s, int size, FILE *stream); char * gets( char *s ); int fputs(const char *s, FILE *stream); int puts(const char *s);Erreur classique, la taille de la zone mémoire pour recevoir la chaine est insuffisante suite à une mauvaise allocation ou déclaration. En particulier, gets() ne peut pas être recommandée pour coder dans la vrai vie !
Hey Roxan, you dont have to printf this string... just puts it ![skip ] L'instruction printf : comprendre affichage formaté, vous permet d'imprimer vos données. La section 3 des pages du manuel décrit avec précision l'ensemble des possibilités de cette puissante (si si)fonction, nous nous contenterons de quelques exemples.
-- police
int printf( const char * format, ...); int fprintf( const char * format, ...); int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...);Notons que les fonctions retournent le nombre d'octets écrits. Notons aussi que le prototype permet un nombre variable d'arguments, ces fonctions sont dites variadiques, cf. les va_list.
char fn[64]; snprintf( fn, 64, "ouput-%d.txt", getpid() ); src = fopen( fn, "r"); if ( ! src ) { fprintf( stderr, "no file!"); exit(1); }
warning: too many arguments for formatou bien les types sont incompatibles
warning: unsigned int format, double arg (arg 2).
#includeint main(void) { int z; float pi; char chaine[12] = "Hello"; z = 17; printf("\nEcrire un entier :"); printf("\nen decimal %d, en octal %o, en hexa %x %X",z,z,z,z,z); //Ecrire un entier : decimal 17, en octal 21, en hexa 11 11 printf("\nSur 4, 3, 2,1 colonnes |%4d|%3d|%2d|%1d|",z,z,z,z); //Sur 4, 3, 2,1 colonnes | 17| 17|17|17| z = -1; printf("\nnon signe %u", z); //non signe 4294967295 z = 65; printf("\n%d est le code ASCII de %c", z, z); //65 est le code ASCII de A pi = 3.1415; printf("\nEcrire un rationnel :"); printf("\nNotation scientifique %e du nombre %f", pi, pi); printf("\navec deux chiffres apres la virgule %f.2", pi); //Ecrire un rationnel : //Notation scientifique 3.141500e+00 du nombre 3.141500 //avec deux chiffres apres la virgule 3.141500.2 printf("\nEcrire une chaine :"); printf("%s world !\n", chaine); //Ecrire une chaine :Hello world ! printf("\nEcrire une adresse :"); printf("\nQuelques adresses %p %p %p", chaine, &(chaine[0]), &(chaine[1])); //Ecrire une adresse : //Quelques adresses 0xeffff8a8 0xeffff8a8 0xeffff8a9 printf("\n"); }
char *p="char *p=%c%s%c;main(){printf(p,34,p,34);}";main(){printf(p,34,p,34);}
Sometimes, it is better to stay home in bed on Monday, than to spend the whole week debugging the code that was written on Monday.[skip ] L'opération de lecture scanf est encore plus puissante que l'opération duale printf. Comme son nom l'indique scanf permet de scanner les données suivant un format :
-- Christopher Thompson
int scanf(const char * format,arg1, arg2, ?) int fcanf(FILE*src, const char *format,arg1, arg2, ?) int sscanf(char * str, const char *format, arg1, arg2, ?)où les argi sont des adresses.
La procédure scanf tente de lire une donnée au format spécifié, renvoie 0 si c'est impossible, sinon un entier positif correspondant au nombre de valeurs lues. Dans ce cas les caractères sont absorbés. Par défaut les espaces (blancs, tabulation, et saut de ligne) sont des séparateurs. La plupart du temps scanf laisse ces séparateurs sur le flux stdin ce qui explique par exemple le mauvais fonctionnement de :
< while ( car != 'N') { puts("\nUne chaine :"); scanf("%s", str); printf("\n%s contient %d caractères",str,strlen(str) ); puts("\nEncore une fois ?"); car = getchar(); //car ne sera jamais égal au dernier caractère saisi ! //mais toujours au dernier caractèrelaissé par scanf sur le flux //stdin. Vérifions printf("%d", car); //affiche 12 car = getchar(); //maintenant c'est ok. }Continuons par un exemple assez complet. Supposons que le programme C ci-dessous s'appelle scan.c. Le résultat de la commande :
gcc scan.c echo '123 azerty 1789 a1bc34&56a' | a.out Un nombre au format decimal Une chaine et un nombre : Ta chaine azerty, ton nombre 1789 et maintenant scannons... [lettres a] [chiffres 1] [lettres bc] [chiffres 34][...] [chiffres 56] [lettres a]
#include#include int main( void) { int z; char str[100]; puts("\nUn nombre au format decimal :"); if ( ! scanf("%d", &z) ) printf("Pas de nombre a lire !"); puts("Une chaine et un nombre :"); scanf("%s %d", str, &z); printf("\nTa chaine %s, ton nombre %d",str,z); puts("\net maintenant scannons..."); while ( 1 ) { if ( scanf("%[0-9]*",str)) printf("\n[chiffres%12s]", str); else if ( scanf("%[a-z]*", str))printf("\n[lettres %12s]", str); else { scanf("%[^0-9a-z]*", str);puts("[...]");} }
// [ECO "A50"] sscanf(yytext, "[ECO%*[ ]\"%[^\"]\"]", &eco );Le modifieur '*' signifie que l'on ne souhaite pas récupérer la séquence d'espaces qui précède le caractère guillemet.
There are only 10 types of people in the world: Those who understand binary, and those who don't ![skip ] Les fonctions
-- Ben Harry
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);permettent la lecture et l'écriture dans les fichiers binaires.
int main(int argc, char* argv[]) { FILE* src, *dst; int index = 0; short buffer[1024]; src = fopen ( argv[1], "r"); dst = fopen ( argv[2], "w" ); while ( EOF != fscanf( src ,"%hd", &buffer[index++]) ); fwrite( buffer, sizeof (int ), index, dst ); fclose( src ); fclose( dst ); return 0; }
$ for x in {1..64}; do echo $x; done > fic.txt $ hexdump -d -n12 fic.bin $ hexdump -d -n8 fic.txt 0000000 02609 02610 02611 02612 0000008 $ hexdump -c -n8 fic.txt 0000000 1 \n 2 \n 3 \n 4 \n 0000008 ./a.out fic.txt fic.bin $ hexdump -c -n8 fic.bin 0000000 001 \0 002 \0 003 \0 004 \0 0000008 $ hexdump -d -n8 fic.bin 0000000 00001 00002 00003 00004 0000008 $ ls -alt fic.* -rw-rw-r--. 1 drmichko drmichko 260 3 avril 11:23 fic.bin -rw-rw-r--. 1 drmichko drmichko 183 3 avril 11:21 fic.txt
I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone.[skip ] La fonction perror() affiche un message sur la sortie d'erreur standard, décrivant la dernière erreur rencontrée durant un appel système ou une fonction de bibliothèque.
-- Bjarne Stroustrup
#include#include /* (1:2) (1:4) */ int main(int argc, char* argv[]) { FILE* src; int p = 0, s = 0, z; src = fopen ( argv[1], "r"); if ( ! src ) { perror("main"); exit( 1 ); } perror(""); while ( ! feof( src ) ) { switch ( fscanf(src, "(%d:%d)", &z, &z) ) { case 2 : p++; break; case 1 : s++; break; case 0 : fgetc( src ); } } fclose( src ); printf("\n%d paires, %d entiers.\n", p, s ); return 0; }
$ ./a.out xxx main: No such file or directory $ touch xxx $ chmod u-r xxx $ ./a.out xxx main: Permission denied ./a.out p.c Success 2 paires, 1 entiers.
Evolution of the C programmer:[skip ] L'instruction
0 months to 1 month: complete beginner
1 month to 1 year: incomplete beginner
1 year to 2 years: acolyte
2 years to 3 years: adept
3 years to 8 years: expert
at 8 years: discovers comp.lang.c
8 years+: buggrit, back to beginner again !"
— Richard Heathfield.
enum chiffre {zorro, un, deux, trois, quatre, cinq, six, sept, huit, neuf};à pour effet la définition du type enum chiffre et 10 constantes de ce type dont les valeurs seront respectivement 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. L'identificateur chiffre est optionnel.
enum chiffre c;On peut définir un type sifr par :
typedef enum chiffre sifr;et déclarer :
sifr c;
If it wasn't for C, we'd be writing programs in BASI, PASAL, and OBOL.[skip ] Les caractères sont cod\'es sur des octets ( char ). Le codage ascii établit depuis fort longtemps la correspondance entre les entiers de 7-bits et les caractères :
$ man ascii Pour plus de commodité, nous vous fournissons des tables plus compactes en hexadécimal et en décimal. 2 3 4 5 6 7 30 40 50 60 70 80 90 100 110 120 ------------- --------------------------------- 0: 0 @ P ` p 0: ( 2 < F P Z d n x 1: ! 1 A Q a q 1: ) 3 = G Q [ e o y 2: " 2 B R b r 2: * 4 > H R \ f p z 3: # 3 C S c s 3: ! + 5 ? I S ] g q { 4: $ 4 D T d t 4: " , 6 @ J T ^ h r | 5: % 5 E U e u 5: # - 7 A K U _ i s } 6: & 6 F V f v 6: $ . 8 B L V ` j t ~ 7: ´ 7 G W g w 7: % / 9 C M W a k u DEL 8: ( 8 H X h x 8: & 0 : D N X b l v 9: ) 9 I Y i y 9: ´ 1 ; E O Y c m w A: * : J Z j z B: + ; K [ k { C: , < L \ l | D: - = M ] m } E: . > N ^ n ~ F: / ? O _ o DELPour les travaux sur les caracères, pensez à utiliser les fonctions prédéfinies du ctype.h
$ man isalpha #includeint isalnum(int c); int isalpha(int c); int isascii(int c); int isblank(int c); int iscntrl(int c); int isdigit(int c); int isgraph(int c); int islower(int c); int isprint(int c); int ispunct(int c); int isspace(int c); int isupper(int c); int isxdigit(int c);
One of the best programmers I ever hired had only a High School degree; he's produced a lot of great software, has his own news group, and made enough in stock options to buy his own nightclub.[skip ] Les chaines de caractères permettent la manipulation des mots. En langage C une chaine est une zone mémoire contenant impérativevement le caractère de code ascii 0 qui indique alors la fin du mot. Une chaine constante s'écrit entre deux guillemets.
--Peter Norvig
char *x = "hello"; char y[6] = {'w','o','r','l','d'};Attention, lors de manipulation de chaine, il faut veiller à ce que la taille des tableaux soient suffisantes. D'une manière générale, il est préférable d'utiliser des pointeurs char * plutôt que des tableaux char t[]. Un fait qui illustré dans les fonctions suivantes :
void SansEspace( char dst [], char src [] ) { int i, j; j = 0; i = -1 ; do { i = i + 1; if ( ! isspace( src[i] ) ) dst[j++] = src[i]; } while ( src[i] ); }
void SansEspace( char *dst , char *src ) { do if ( ! isspace( *src ) ) dst++ = *src; while ( *src++); }Les fonctions usuelles du string.h :
int strlen( char * src) : retourne la longueur de la chaine src int strcpy( char *dst, char* src) : copie de src dans dst int strncpy(char *dst, char* src, int n) : copie au plus n caractères de src vers dst int strdup(char *dst, char* src) : duplication : allocation + copie de src dans dst int strndup(char *dst, char* src, int n) : int strcat( char *dst, char* src) : concaténation de src à dst int strcmp(char *s, char* t) : comparaison de s et t int strncmp(char *s, char *t, int n) : comparaison de s et t sur au plus n caractères int atoi(char *s) : conversion d'une chaine en entier long int atol(char *s) : conversion d'une chaine en un entier long
[ "hip", "hip" ][skip ] Les variables de type tableaux représentent zones mémoires indexées. Etant donné un type objet les déclarations :
(hip hip array!)
objet t[16]; objet m[4][4]; objet x[2][2][2][2];définissent trois tableaux de 16 objets. Quelle que soit la dimension, un tableau définit un espace mémoire compact :
int t[3][3]; int i, j, v=0; int *p; for( i = 0; i < 3; i++) for( j = 0; j < 3; j++) t[i][j] = v++; p = & t ; for( i = 0; i < 9; i++, p++) printf(" %d", *p);
$ ./a.out 0 1 2 3 4 5 6 7 8Un tableau peut être initialisé par un tableau constant :
int z[1024] = { 0 }; int t[2][3] = { {1, 2, 3}, {4, 5, 6} };Il est possible de créer des tableaux dynamiques sur la pile :
void proc( int k, int n) { int t[k][n]; ... }Mais pour des raisons assez évidentes :
void proc( int n) { int t[n] = { 0 }; }
$ gcc -Wall x.c x.c: In function ‘proc’: x.c:3: erreur: un objet de taille variable peut ne pas être initialisé x.c:3: attention : éléments en excès dans l'initialisation de tableau x.c:3: attention : (near initialization for ‘t’) x.c:3: attention : unused variable ‘t’Il faudra faire une boucle d'initialisation ou encore utiliser une fonction memset. Enfin, le prototype
void proc( int t[] ) { }définit une fonction qui acceptera tous les tableaux à une dimension, ainsi que les pointeurs vers les entiers.
Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.[skip ] La déclaration
--Alan Kay
struct fiche { int age; char* name; } x;déclare une structure fiche et une variable à deux champs.
struct fiche select( struct fiche x, struct fiche y ) { if ( x.age < y.age ) return x; return y; }On peut introduire plus de sémantique dans le code en déclarant un nouveau type :
typedef struct { int age; char* name; } fiche;déclare un type fiche.
fiche select( fiche x, fiche y ) { if ( x.age < y.age ) return x; return y; }
The evolution of languages: FORTRAN is a non-typed language. C is a weakly typed language. Ada is a strongly typed language. C++ is a strongly hyped language.[skip ] Dans certains cas, nous pouvons avoir besoin de manipuler les champs d'une structure de différentes façons en fonction d'un contexte.
-- Ron Sercely
union { long long val; char byte[8]; } x;déclare une variable x avec deux champs superposés, la taille de x sera probablement de 8 octets, et non pas 16, car les champs val et byte sont superposés.
x.val = 1 + (2<<8) + (3<<16) + (4<<24); if ( x.byte[0] == 1 ) puts('little endian'); else puts('big endian');
& Joe...[skip ] Les variables de type pointeurs représentent des adresses. Etant donné un type objet, une variable x de type objet, l'adresse de x est de type objet*, ainis la déclaration :
-- Jimi
objet *p; objet t[16];défini un pointeur vers un objet. L'arithmétique des pointeurs est faite pour rendre compatible les notions de tableau et de pointeur :
p = & t[0]; p++; if ( *p != t[1] ) { puts("Vous avez découvert un sérieux bug!"); exit( 1 ); }L'opérateur -> permet d'accéder aux champs des structures pointées
typedef struct ptr { int seg, ofs; } void inc( struct ptr * a ) { a->ofs ++; }Une allocation permet d'obtenir un tableau de taille dynamique dont la taille est déterminée au cours de l'exécution :
objet * getobjet( int n ) { objet *r; r = calloc( n, sizeof(objet) ); if ( r ) return r; perror("getobjet"); exit(1); }Depuis C90, le transtypage :
r = ( objet*) calloc( n, sizeof(objet) );est devenu optionnel.
typedef struct { int x; char y; } objet;mon gcc produit un objet de taille 8. L'attribut packed permet de contrôler la taille des types. Notons que les tableaux à deux dimensions gagnent à être vus comme des tableaux de lignes pour faciliter les permuations des lignes.
int** mat; mat = calloc( k, sizeof(int*) ); for( i = 0; i < k; i++) mat[i] = calloc( n, sizeof(int) );Enfin, une récursion permet de construire des listes chainées :
typedef struct _list_ { int val; struct _list_ * next, * pred; } enr, *list;
int getrandprime( void ) { return 4; // choosen by fair dice roll // an odd prime ( to be checked ). }
[skip ] Le type fonction est un peu déroutant de prime abord, mais la description d'un pointeur de fonction retournant un entier et prenant deux arguments entiers :
-- an inventive student.
int( * )(int *, int *)suit les règles habituelles.
int f; -- adresse --> int *f; int f (int x); -- adresse --> int (*f) (int x ); int* f( int x); -- adresse --> int* (*f) (int x);En cas d'oubli, pensez à gdb :
Reading symbols from /home/pl/a.out...done. (gdb) l 1 int id( int i ) 2 { return i; } 3 int main() { } (gdb) whatis id type = int (int) (gdb) whatis &id type = int (*)(int) (gdb) call id(7) You can't do that without a process to debug. (gdb) b main Breakpoint 1 at 0x804839f: file x.c, line 3. (gdb) run Starting program: /home/pl/a.out Breakpoint 1, main () at x.c:3 3 int main() { } Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686 (gdb) call id(7) $1 = 7 (gdb) call &id(7) Attempt to take address of value not located in memory. (gdb) call (&id)(7) $2 = 7
int id( int x) { return x ;} int sigma( int (*f) (int), int n) { int res = 0; while ( --n >= 0) res += f(n); return res; }Le lecteur vérifiera que sigma(id, 100) retourne la bien 4950.
$ echo 's=0;for(i=0; i < 100;i++) {s+=i}; s;' | bc 4950Notons tout de même qu'une définition de type permet d'y voir plus clair :
typedef double reel; typedef reel fonction( reel x ); reel solve( fonction f, real a, reel b) { ... bla ... bla y = f( a + b ); ... bla } fonction *test = sin; r = solve ( test, a, b ); test( r );L'usage n'est pas trés courant, citons l'exemple du tri rapide :
$ man qsort NOM qsort - Trier une table SYNOPSIS #includevoid qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
A good system can't have a weak command language.[skip ] Les fonctions variadiques s'appliquent à un nombre de paramètres indéfinis. Toutefois, ce nombre est connu à la compilation et doit être passé à la fonction comme dans l'exemple qui suit, ou bien de façon implicite par un format comme dans printf. Le traitement des arguments (va_list) se fait par l'usage de quatres macros, consulter le manuel man stdarg.h
-- Alan Perlis
#includeint sum(int argc, ...){ va_list ap; int res = 0; va_start(ap, argc); while ( argc--) res += va_arg(ap, int ); va_end(ap); return res; } int main( void ) { return sum( 3, 1, 2, 3 ); }
$ ./a.out $ echo $? 6
One man's constant is another man's variable.[skip ] Il s'agit de déclarer des variables ouverte en lecture, fermées en écriture. Il s'agit plus d'un garde-fou (important) pour le programmeur, ou d'une information (précieuse) pour l'utilisateur de code. Exemple : la déclaration size_t strlen(const char *s) nous informe que la fonction strlen ne modifie pas les s[i], c'est utile pour l'utilisateur de cette fonction, et une protection si strlen était en développement.
-- Alan Perlis
const int age = 50; // difficile a croire ! #define age 50 // une constante 50 pas d'existence dans le code.
age++;Le code est incompilable :
In function ‘main’: erreur: increment of read-only variable ‘age’Garde-fou disais-je ? En effet,
int *x = & age; (*x)++; if ( *x != age ) printf("happy birthday !");Une petite méditation sera nécessaire consternant les déclarations :
const char *s; // les s[i] sont proteges char * const s; // s est protege const char * const s;
Software is getting slower more rapidly than hardware becomes faster.[skip ]
-- Nicolas Wirth
extern int z;nous signalons que la variable z de type int est dans un autre module. Dès lors, le compilateur peut vérifier la sémantique du code et produire du code objet.
register int z;nous suggérons au compilateur de placer la variable locale z dans un registre. Notons bien que ceci est rarement une bonne idée !
int sum( int l) { #ifdef REGISTER register int r; #else int r; #endif int s; for( r = 0; r < l; r++) s += r; return s; }
$ gcc -Wall -c z.c $ objdump -S z.o 00000000Les variables r et s sont placées sur la pile.: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 10 sub $0x10,%esp 6: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp) d: eb 0a jmp 19 f: 8b 45 fc mov -0x4(%ebp),%eax 12: 01 45 f8 add %eax,-0x8(%ebp) 15: 83 45 fc 01 addl $0x1,-0x4(%ebp) 19: 8b 45 fc mov -0x4(%ebp),%eax 1c: 3b 45 08 cmp 0x8(%ebp),%eax 1f: 7c ee jl f 21: 8b 45 f8 mov -0x8(%ebp),%eax 24: c9 leave 25: c3 ret
$ gcc -Wall -DREGISTER -c z.c $ objdump -S z.o 00000000La variable r est placée dans le registre bx.: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 53 push %ebx 4: 83 ec 10 sub $0x10,%esp 7: bb 00 00 00 00 mov $0x0,%ebx c: eb 06 jmp 14 e: 01 5d f8 add %ebx,-0x8(%ebp) 11: 83 c3 01 add $0x1,%ebx 14: 3b 5d 08 cmp 0x8(%ebp),%ebx 17: 7c f5 jl e 19: 8b 45 f8 mov -0x8(%ebp),%eax 1c: 83 c4 10 add $0x10,%esp 1f: 5b pop %ebx 20: 5d pop %ebp 21: c3 ret
static int z;nous demandons au compilateur de placer la variable z dans le segment de données, pas sur la pile. La port\'e de l'objet demeure au niveau du bloc, contrairement aux apparences, elle ne sera initialisée qu'une fois.
void proc( int k ) { static int z=13; int x; if ( k == 0 ) return; printf("\nk=%d x=%d z=%d", k, x, z); x++; z++; k--; proc( k ); } main( ) { proc( 3 ); }L'output est sans surprise :
$ ./a.out k=3 x=0 z=13 k=2 x=57 z=14 k=1 x=0 z=15Un abc pour comprendre le cas des variables globales :
[pl@epsilon ~]$ cat a.c #includealles klar ?static x; int y; int z; void A( ) { printf("\nA:x%ps y%p. z%p.", &x, &y, &z); } [pl@epsilon ~]$ cat b.c #include int x; static int y; int z; void B( ) { printf("\nB:x%p. y%ps z%p.", &x, &y, &z); } [pl@epsilon ~]$ cat c.c #include void A( void ); void B( void ); extern int x, y, z; int main( ) { A(); B(); printf("\nC:x%p y%p z%p\n", &x, &y, &z); return 0; } [pl@epsilon ~]$ gcc -c a.c b.c [pl@epsilon ~]$ gcc a.o b.o c.c [pl@epsilon ~]$ ./a.out A:x0x80497b0s y0x80497bc. z0x80497b8. B:x0x80497c0. y0x80497b4s z0x80497b8. C:x0x80497c0 y0x80497bc z0x80497b8
volatile int cali;nous signalons au compilateur que la variable peut changer de valeur spontanément, et donc, ce dernier ne doit pas envisager d'optimisation sur cette variable. La volatilité un phénomène surprenant lié au rayonnement cosmique ? Pourquoi pas... Mais plus raisonnablement lors d'une interruption !
void proc( void ) { #ifdef VOLATILE volatile int cali = 0; #else int cali = 0; #endif while ( cali != 0 ) cali--; }On commence par une compilation banale
$gcc -g -c a.c $objdump -S a.o 00000000gcc ne fait pas d'optimisation, il compile ce qui est écrit, en particulier, une boucle inutile ! Maintenant, on demande à gcc d'optimiser: void proc( void ) { 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 10 sub $0x10,%esp #ifdef VOLATILE volatile int cali = 0; #else int cali = 0; 6: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp) #endif while ( cali != 0 ) cali--; d: eb 04 jmp 13 f: 83 6d fc 01 subl $0x1,-0x4(%ebp) 13: 83 7d fc 00 cmpl $0x0,-0x4(%ebp) 17: 75 f6 jne f } 19: c9 leave 1a: c3 ret
$ gcc -g -c -O2 a.c $ objdump -S a.o 00000000gcc analyse le code, il voit que cali vaut zéro, ne change pas, donc pas de boucle ! Le même chose mais avec un volatile sur cali :: volatile int cali = 0; #else int cali = 0; #endif while ( cali != 0 ) cali--; } 0: f3 c3 repz ret
$ gcc -g -c -O2 -DVOLATILE a.c $ objdump -S a.o 00000000Cette fois-ci, gcc ne fait aucune hypothèse sur cali, la boucle est codée.: void proc( void ) { 0: 83 ec 10 sub $0x10,%esp #ifdef VOLATILE volatile int cali = 0; 3: c7 44 24 0c 00 00 00 movl $0x0,0xc(%esp) a: 00 #else int cali = 0; #endif while ( cali != 0 ) cali--; b: 8b 44 24 0c mov 0xc(%esp),%eax f: 85 c0 test %eax,%eax 11: 74 18 je 2b 13: 90 nop 14: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi 18: 8b 44 24 0c mov 0xc(%esp),%eax 1c: 83 e8 01 sub $0x1,%eax 1f: 89 44 24 0c mov %eax,0xc(%esp) 23: 8b 44 24 0c mov 0xc(%esp),%eax 27: 85 c0 test %eax,%eax 29: 75 ed jne 18 } 2b: 83 c4 10 add $0x10,%esp 2e: c3 ret
Par ordre de priorités :
<= négation !, complément à 1 ~, incrémentation ++, décrémentation --, opposition -, indirection *, adresse &, taille sizeof, et transtypage (type).
=> multiplication *, division / et réduction %
=> addition +, soustraction -
=> décalage à gauche << et décalage à droite >>
=> comparaisons <, <= , > et >=
=> égalité == différent !=
=> ET bit à bit &
=> OU exclusif bit à bit ^
=> OU bit à bit |
=> ET logique &&
=> OU logique | |
<= alternative ? :
<= affectations = *= /= %= += -= <<= >>= &= ^= |=
=> virgule ,
toc... toc... toc...[skip ] En Langage C nous disposons de quatre structures alternatives.
C'est ici cours de C ?
If if sir, please, between !
L'instruction alternative incomplète :
if ( condition ) instructions;
if ( condition ) instructions; else instructions;
x = test ? yes : no;Une écriture rapide et compacte de :
if (test) x = yes; else x = no;et une instruction de sélection.
void letsgo(FILE *src ) { int num = 0; char line[1024]; while ( ! feof (src) ) { line[0] = '\0'; fgets (line, 1024, src); num++; switch ( *line ) { case '#': // skip comments break; case 'f' : play( line ); break; case 'm': sscanf( line, "mode=%d", & MODE ) break; case 'p': if ( 2 != sscanf( line, "parm=%d %d", & X, &Y ) ) { fprintf( stderr, "error : %d : %s", num, line); exit(1); } break; default:; } } fclose (src); }Pour comprendre, l'approche calculatoire de gcc, je vous suggère de jeter un oeil aux codes assembleurs produit par le compilateur.
int test(int x) { int y; switch (x) { case 0: case 4: y = 3; break; case 1: case 5: y = 1; break; case 3: case 7: y = 2; break; case 2: case 6: y = 3; break; default: y = 0; } return y + x; }Erreur : mauvaise gestions des breaks. Le break est optionnel. Une fois le branchement fait, les instructions sont exécutées jusqu'au premier break qui fait sortir du bloc. Le compilateur génère des sauts calculés, c'est simple et efficace :
[pl@localhost ~]$ gcc -S x.c [pl@localhost ~]$ cat x.s .file "x.c" .text .globl test .type test, @function test: pushl %ebp movl %esp, %ebp subl $16, %esp cmpl $7, 8(%ebp) ja .L2 movl 8(%ebp), %eax sall $2, %eax movl .L7(%eax), %eax jmp *%eax .section .rodata .align 4 .align 4 .L7: .long .L3 .long .L4 .long .L5 .long .L6 .long .L3 .long .L4 .long .L5 .long .L6 .text .L3: movl $3, -4(%ebp) jmp .L8 .L4: movl $1, -4(%ebp) jmp .L8 .L6: movl $2, -4(%ebp) jmp .L8 .L5: movl $3, -4(%ebp) jmp .L8 .L2: movl $0, -4(%ebp) .L8: movl 8(%ebp), %eax movl -4(%ebp), %edx leal (%edx,%eax), %eax leave ret .size test, .-test .ident "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)" .section .note.GNU-stack,"",@progbits
The C-loops are so cool ![skip ] C propose trois structures itératives : une boucle tant que, une boucle répéter et une boucle for qui tient plus de la boucle tant que que de la boucle pour du langage pascal et assimilés.
-- Pavle Michko
while ( condition ) instructions;Par exemple, la valuation dyadique de x
v = 0; while ( x % 2 == 0 ) { x = x / 2; v = v + 1; }syntaxe : oubli des parenthèses
for ( initialisation; condition; iteration ) instructions;C'est l'abréviation de
initialisation; while (condition) itération;Par exemple, le poids de x :
for( w = 0; x > 0 ; w++ ) x &= x - 1;syntaxe : confusions entre virgule et point virgule
do instructions while ( condition );Par exemple, l'ordre multiplicatif de p modulo n :
f = 0; x = 1; do { x = ( x * p ) % n; f = f + 1; } while ( x != 1 );
Hardware : The parts of a computer system that can be kicked.[skip ] Rarement souhaitées, les boucles infinies ont des sources diverses. En voici quelques exemples :
-- Jeff Pesis
unsigned z; z = 10000; while ( z >= 0 ) z--; int z; z = 1; while ( z < 0x7FFFFFFF ) z *= 2; char c; int z; z = 357; c = 0; while ( c < z ) c++;Quelle idée d'itérer avec un float ? Mais oui, quand on enseigne, on voit de tout : un étudiant m'a déjà fait ça !
float x, y, p; x = 0; y = 1; p = 0.1; //avec un pas p = 0.125, la boucle termine. while ( y != x ) y = y - p;
int main(void) { char table[8]; char i; i = 0; while ( i++ <= 10 ) table[i] = 0; return 0; }Dans tous les cas, il est possible d'utiliser gdb pour intercepter un processus suspecté de boucler à l'infini :
$ ./a.out % ps -C a.out PID TTY TIME CMD 6823 pts/0 00:00:06 a.out % gdb --pid 6823 Attaching to process 6823 Reading symbols from /home/pl/a.out...done. Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done. Loaded symbols for /lib/ld-linux.so.2 0x080483b4 in main () at x.c:12 12 while ( i++ <= 10 ) Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686 (gdb) display i 1: i = 3 '\003' (gdb) n 13 table[i] = 0; 1: i = 3 '\003' (gdb) 12 while ( i++ <= 10 ) 1: i = 3 '\003' (gdb) 13 table[i] = 0; 1: i = 4 '\004' (gdb) 12 while ( i++ <= 10 ) 1: i = 4 '\004' (gdb) 13 table[i] = 0; 1: i = 5 '\005' (gdb) 12 while ( i++ <= 10 ) 1: i = 5 '\005' (gdb) 13 table[i] = 0; 1: i = 6 '\006' (gdb) 12 while ( i++ <= 10 ) 1: i = 6 '\006' (gdb) 13 table[i] = 0; 1: i = 7 '\a' (gdb) 12 while ( i++ <= 10 ) 1: i = 7 '\a' (gdb) 13 table[i] = 0; 1: i = 8 '\b' (gdb) 12 while ( i++ <= 10 ) 1: i = 0 '\000' (gdb) 13 table[i] = 0; 1: i = 1 '\001'Alors, c'est clair ?
The unavoidable price of reliability is simplicity.[skip ] En dehors de return, le langage C prévoit trois ruptures de séquences : break,continue et goto. On utilise break pour sortir d'un bloc itératif et l'instruction continue pour se brancher à la fin du bloc itératif.
--C.A.R HOARE
while ( --x > 0 ) { y = f(x); if ( y == 0) break; else s = s + 1/y; }
while ( --x > 0 ) { y = f(x); if ( y == 0 ) continue; s = s + 1/y; }go to
Please don't fall into the trap of believing that I am terribly dogmatical about [the goto statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!
-- Edsger Dijkstra
If you want to go somewhere, goto is the best way to get there.[skip ] Effectivement, le langage C conserve la possibilité de débrancher à base de goto. L'utilisation est assez rare, mais dans le cadre d'une programmation automatique pour un dévissage de récursion par exemple, le code n'a pas lieu de rester clair pour un humain.
-- Ken Thompson.
while ( 1 ) { x = choice(); if ( dejavu( x ) ) continue; marque( x ); while ( test(x) ) { if ( good(x) ) goto yes; if ( bad(x) ) break; x = next(x); } } yes:
We should forget about small efficiencies, say about 97% of the time:premature optimization is the root of all evil[skip ] Bon voilà, j'ai écrit un programme prog.c qui ne marche pas :-(... Je comprend pas ce qui se passe. Je l'ai compilé
-- Donald Knuth
gcc -o prog prog.cIl n'y a aucune erreur et pourtant quand je lance l'exécutable, je récolte le message :
Bus error (core dumped)ou encore
Segmentation fault (core dumped)J'ai beau recompiler plusieurs fois rien ne change ! Que se passe-t-il ?
Je ne sais pas ... Mais commencez par compiler votre programme avec l'option -Wall grace à laquelle, le compilateur gcc d'ordinaire peu locace est rendu plus bavard. Essayez...
gcc -Wall -o prog prog.cAlors ? ce beau prog.c est-il vraiment socorrect ? Dans une phase de développement, l'option -Wall est indispensable mais encore insuffisante, je vous suggère :
gcc -Wall -g -Wuninitialized -O1 -Wunused-parameterL'option -g permettra le debugage éventuel avec gdb, les deux avertissements sont clairs, notez que (-Wuninitialized) requiert l'option -Ox (optimisation) pour être activée. Consultez les manuels directement sur le site http://gcc.gnu.org. Rappelez vous bien que la compilation d'un programme correct ne devrait signaler aucun avertissement !
There are two major products that come out of Berkeley: LSD and UNIX. We don’t believe this to be a coincidence.[skip ]
-- Jeremy S. Anderson
La mise au point d'un programme demande plusieurs tentatives de compilations au moins une (expert) mais parfois plusieurs dizaines. Alors plutôt que de taper cent fois la commande gcc -Wall -o prog prog.c, je vous suggère de créer un tout petit fichier makefile nommé qui contient les lignes
prog : prog.c gcc -Wall -o prog prog.cIl suit le schéma cible : dépendances, règles.
Pour lancer la compilation, il suffit d'exécuter l'utilitaire make qui recherche le fichier Makefile ou makefile dans le répertoire courant et exécute les instructions spécifiées.
$ make gcc -Wall -o prog prog.c $ make make: « prog » est à jour.
Ici, on indique que le fichier prog dépend de la source prog.c et si ce dernier est plus récent que prog alors une compilation est lancée sinon rien n'est fait... Ecrire des fichiers makefile c'est un petit peu comme se brosser les dents, c'est une bonne habitude à prendre et gare aux caries ! L'approche dépasse largement le cadre de la compilation d'une source C. Dans l'exemple qui suit, make pi.png, fait une belle (bof) image !
SHELL=/bin/bash MAX=1000 pi.exe : pi.c gcc -Wall pi.c -o pi.exe pi.png : pi.exe ./pi.exe $(MAX) > data.txt gnuplot <<< "set out 'pi.png';\ set term png;\ plot 'data.txt' w l, x/log(x)+sqrt(x);"
int main( int argc, char*argv[]) { int i, j, s=0, n = atoi( argv[1]); char* t = malloc( n ); for( i = 2; i < n; i++ ){ if ( 0 == t[i] ){ j = i*i; s++; while ( j < n ) t[j+=i]=1; } printf("%d %d\n", i, s); } return 0; }Les variables automatiques internes:
$@ Le nom de la cible $< Le nom de la première dépendance $^ La liste des dépendances $? La liste des dépendances plus récentes que la cible $* Le nom du fichier sans suffixeRègles implicites:
SRC=$(wildcard *.txt) PNG=$(SRC:.txt=.png) all : $(PNG) %.png: %.txt ./mkstat.sh $<
Beware of bugs in the above code; I have only proved it correct, not tried it ![skip ]
-- Donald Knuth.
Si vous êtes arrivés jusque là c'est que votre programme est acceptable du point de vue de GCC aucun message de mise en garde, aucune alerte... Bravo ! Mais non!!! Le programme ne fonctionne toujours pas... Toujours ce satané bus error ou encore ce segmentation fault... Un message qui signifie que vous tentez un accès mémoire dans une zone interdite affectation invalide : débordement de tableau, pointeur mal alloué, débordement de pile ou encore un calcul erroné.
Un fichier core témoigne des dégâts, voir la section correspondante.
Pour trouvez la ligne qui contient l'instruction incriminée, vous devez recompiler votre programme avec l'option de debogage -g puis lancer l'utilitaire de debogueur.
gcc -g -o prog prog.c gdb prog
Le prompt gdb apparait introduit par un message plus ou moins loin. Pour une petite v isite de mise en train essayez la commande help, puis la commande help running. L'une des commandes listées permet l'exécution contrôlée du programme. Laquelle? run of course. Tapez donc run. Le programme s'arrête sur la ligne qui contient l'erreur à vous de diagnostiquer puis de corriger !
(gdb) run Program received signal SIGSEGV, Segmentation fault 0x106e4 in main () at mbp.c:8 8 table[j] = iqui indique une erreur en ligne 8. La valeur de j doit-etre en dehors des limites du tableau. Pour le vérifier, nous pouvons imprimer j puis le type de la variable table.
(gdb) print j 1000 (gdb) whatis table type = int [10]
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.[skip ] Les commandes de bases de gdb dans l'ordre d'apprentissage probable.
--Kernighan
run |
lancer le programme |
run source.txt |
list> | lister des lignes |
list 10 |
break ... if delete |
placer un point d'arrêt supprime |
break 30, break main, break 12 if
x
== 3 delete 4 |
display undisplay |
tracer une variable |
display x, display $pc, display (int) *x |
print |
afficher une variable |
print x, print /t x |
help |
help print |
|
next |
exécuter l'instruction
suivante |
|
step |
idem. y compris dans ss-programme |
|
cont |
continuer jusquau prochin
arrêt |
|
info |
information |
info break, info registers |
whatis |
type d'une expression |
whatis *x |
watch |
surveiller les changements
de
valeurs |
watch x |
enable desable |
gestion des points |
enable 6 desable 3 |
disassemble |
code machine |
disassemble main |
/* when I wrote this, only God and I understood what I was doing. Now God only know ! #BeAGeek */[skip ] En cas d'erreur, un fichier core peut-être créé dans le répertoire courant ou ailleurs. Vous pouvez l'utiliser pour faire un diagnostic sans relancer votre programme. Un certain programme prog.c compilé avec l'option -g provoque l'erreur d'exécution :
./a.out Segmentation fault (core dumped)Pour en savoir plus, il suffit d'invoquer gdb, sans oublier le fichier core en offrande.
gdb a.out core This GDB was configured as "sparc-redhat-linux"... warning: "/home/langevin/CC/CANL/core": no core file handler recognizes format, using default Core was generated by `./a.out '. Program terminated with signal 11, Erreur de segmentation. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x10ea4 in err (x=7) at dtest.c:13 13 *ptr = x;L'erreur est en ligne 13, adressage invalide !
(gdb) bt #0 0x10ea4 in err (x=7) at dtest.c:13 #1 0x120d8 in main (argc=3,argv=0xeffffd44) at dtest.c:2Listez la source au voisinage de la ligne 13 !
(gdb) l 13 8 9 int DIM=7, GOOD=0; 10 11 void err( int x ) 12 { int *ptr; 13 *ptr = x; 14 } 15
confirmez(vous le diagnostic ?
If debugging is the process of removing bugs, then programming must be the process of putting them in.[skip ] La fonction main sert d'interface entre le système et le programme. Lors du lancement du processus, les variables d'environnements sont empilées, tout comme les arguments placés sur la ligne de commande et le nombre d'arguments. La valeur retournée par la fonction main sera la valeur de retour du processus.
--Edsger Dijkstra
int main( void ) int main( int argc, char*argv[] ) int main( int argc, char *argv[], char* env[] );Exemple,
int main( int argc, char*argv[], char *env[] ) { int i; printf("\nnombre d'arguments : %d", argc ); for( i = 0; argv[i]; i++ ) printf("\nargument %d : %s", i, argv[i] ); for( i = 0; env[i]; i++ ) printf("\nvariable %d : %s", i, env[i] ); return 13; }
$ ./a.out hello world nombre d'arguments : 3 argument 0 : ./a.out argument 1 : hello argument 2 : world variable 0 : XDG_VTNR=1 variable 1 : SSH_AGENT_PID=1794 variable 2 : XDG_SESSION_ID=2 variable 3 : SAL_USE_VCLPLUGIN=gtk variable 4 : HOSTNAME=localhost.localdomain variable 5 : IMSETTINGS_INTEGRATE_DESKTOP=yes variable 6 : TERM=xterm variable 7 : SHELL=/bin/bash variable 8 : XDG_MENU_PREFIX=lxde- variable 9 : BIGLROOT=/home/drmichko/CC/bigloop-0.2 variable 10 : HISTSIZE=1000 variable 11 : XDG_SESSION_COOKIE=66a63c4d633f569190aea4760000000d-1332749738.65811-1829739048 variable 12 : SYSFONT=latarcyrheb-sun16 variable 13 : IMSETTINGS_MODULE=none variable 14 : USER=drmichko variable 15 : LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:*.pdf=00;33:*.ps=00;33:*.ps.gz=00;33:*.txt=00;33:*.patch=00;33:*.diff=00;33:*.log=00;33:*.tex=00;33:*.xls=00;33:*.xlsx=00;33:*.ppt=00;33:*.pptx=00;33:*.rtf=00;33:*.doc=00;33:*.docx=00;33:*.odt=00;33:*.ods=00;33:*.odp=00;33:*.xml=00;33:*.epub=00;33:*.abw=00;33:*.html=00;33:*.wpd=00;33: variable 16 : SSH_AUTH_SOCK=/tmp/ssh-NFlDcCHD1639/agent.1639 variable 17 : PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/drmichko/.local/bin:/home/drmichko/bin variable 18 : MAIL=/var/spool/mail/drmichko variable 19 : DESKTOP_SESSION=LXDE variable 20 : BIGLDIST=/home/drmichko/CC/bigloop-0.2 variable 21 : QT_IM_MODULE=xim variable 22 : PWD=/home/drmichko variable 23 : XMODIFIERS=@im=none variable 24 : LANG=fr_FR.UTF-8 variable 25 : SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass variable 26 : _LXSESSION_PID=1639 variable 27 : HISTCONTROL=ignoredups variable 28 : KEYTABLE=fr-latin9 variable 29 : XDG_SEAT=seat0 variable 30 : HOME=/home/drmichko variable 31 : SHLVL=2 variable 32 : XDG_CONFIG_HOME=/home/drmichko/.config variable 33 : BOOT_IMAGE=/vmlinuz-3.2.7-1.fc16.i686.PAE variable 34 : LOGNAME=drmichko variable 35 : DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-uLZAboDtqZ,guid=106863000a3602b711dd653400000335 variable 36 : LESSOPEN=||/usr/bin/lesspipe.sh %s variable 37 : XDG_RUNTIME_DIR=/run/user/drmichko variable 38 : DISPLAY=:0 variable 39 : XDG_CURRENT_DESKTOP=LXDE variable 40 : XAUTHORITY=/home/drmichko/.Xauthority variable 41 : _=./a.out $ echo $? 13
$ whatis gcc[skip ] Dans l'exemple qui suit, on utilise la fonction getopt pour traiter les options passées en arguments sur la ligne de commande :
gcc (1) - GNU project C and C++ compiler
$ whatis C
C : rien d'adéquat
int main( int argc, char*argv[]) { int opt; char *optlist ="n:b:"; int base = 2, n = 0; while ( ( opt = getopt( argc, argv , optlist) ) > 0 ) switch( opt ){ case 'n': n = atoi( optarg ); break; case 'b': base = atoi( optarg ); break; default : printf("\nusage %s -> %s", argv[0], optlist); return 1; } ... return 0; }
main(){char*a="%cmain(){char*a=%c%s%c;printf(a,10,34,a,34);}";printf(a,10,34,a,34);}[skip ] L'espace mémoire virtuel d'un processus est traditionnellement scindé en 4 zones : donn\'ees, code, pile et tas.
-- un C-quine
char *data = "hello"; void info(char *s, void* t) { printf("\n%10s : %10p ", s, t ); } void proc( int x) { char *y; y = (char*) malloc( 1024 ); info("argument", &x); info("locale", &y); info("sur tas", y); if ( x ) proc(x-1); } int main( void ) { printf("\nptr size : %d", sizeof(int*)); info( "data", &data ); info( "main", &main ); info( "proc", &proc); info( "lib" , &printf); proc( 1 ); getchar(); return 0; }On lance le programme
$ ./a.out ptr size : 4 data : 0x80498b0 main : 0x80484ac proc : 0x8048447 lib : 0x8048320 argument : 0xbfb94b80 locale : 0xbfb94b6c sur tas : 0x9b17008 argument : 0xbfb94b50 locale : 0xbfb94b3c sur tas : 0x9b17410Le processus attend un caractère.
$ ps -C a.out PID TTY TIME CMD 2461 pts/0 00:00:00 a.out $ cat /proc/2461/maps 00d0a000-00d0b000 r-xp 00000000 00:00 0 [vdso] 08048000-08049000 r-xp 00000000 fd:02 3539110 /home/drmichko/a.out 08049000-0804a000 rw-p 00000000 fd:02 3539110 /home/drmichko/a.out 09b17000-09b38000 rw-p 00000000 00:00 0 [heap] 42142000-42163000 r-xp 00000000 fd:01 262860 /lib/ld-2.14.90.so 42163000-42164000 r--p 00020000 fd:01 262860 /lib/ld-2.14.90.so 42164000-42165000 rw-p 00021000 fd:01 262860 /lib/ld-2.14.90.so 42167000-4230e000 r-xp 00000000 fd:01 262867 /lib/libc-2.14.90.so 4230e000-4230f000 ---p 001a7000 fd:01 262867 /lib/libc-2.14.90.so 4230f000-42311000 r--p 001a7000 fd:01 262867 /lib/libc-2.14.90.so 42311000-42312000 rw-p 001a9000 fd:01 262867 /lib/libc-2.14.90.so 42312000-42315000 rw-p 00000000 00:00 0 b77cd000-b77ce000 rw-p 00000000 00:00 0 b77de000-b77e1000 rw-p 00000000 00:00 0 bfb76000-bfb97000 rw-p 00000000 00:00 0 [stack]Avec man proc, vous comprendrez la nature des informations affichées, et l'absence de certaines information. Un point intéressant concerne l'adresse de la fonction printf qui ne semble pas indiquer un point dans une librairie...
gdb --pid 2461 (gdb) x/i 0x8048320 0x8048320Les appels aux librairies dynamiques sont indirects.: jmp *0x8049898 (gdb) x/a 0x8049898 0x8049898 : 0x421b4f60
To understand what recursion is, you must first understand recursion.[skip ]
-- and it may take a while.
La bibliothèque time.h fournit des types, fonctions et procédures relatifs au temps. Indispensable pour mesurer les temps de calculs avec précisions. Votre montre, ou encore la commande unix time peut suffire dans certains cas. Pour des situations plus complexes, le mieus est utiliser un profileur.#include <time.h>
time_t deb, fin;deb = time( NULL );
MaBelleProc();
fin = time( NULL );printf("\nTemps %5.2f sec.", difftime(fin, deb) );
Compilable, fooormidable[skip ]
Tu étais profilable, j'étais debogable
-- maestro
Un profileur est un outil de développement pour analyser la performance d'un code. Le compilateur gcc fait une liason avec le profileur gprof par
gcc -pg prog.c
ce qui a pour effet de surcharger automatiquement
l'exécutable de compteurs d'appels vers les procédure et
de chronomètres. Après exécution, les
données sont enregistrées dans le fichier gmon.out qui doit être
ensuite filtré par gprof.
#includeOn se doute bien que pong() coute plus que ping(). Une preuve avec gprof :double s = 0; void ping( int x ) { s+=x; } void pong( double x ) { s-=x; } void loop( int x ) { while ( x-- ) if ( random()&1 ) ping(x); else pong(x); s=0; } int main( int argc, char* argv[] ) { loop( atoi( argv[1] ) ); return 0; }
gcc -Wall -g -pg g.c ./a.out 100000000 gprof -b
Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 43.33 0.97 0.97 1 0.97 2.25 loop 33.56 1.73 0.76 49997387 0.00 0.00 pong 23.11 2.25 0.52 50002613 0.00 0.00 ping Call graph granularity: each sample hit covers 4 byte(s) for 0.44% of 2.25 seconds index % time self children called name 0.97 1.27 1/1 main [2] [1] 100.0 0.97 1.27 1 loop [1] 0.76 0.00 49997387/49997387 pong [3] 0.52 0.00 50002613/50002613 ping [4] -----------------------------------------------[2] 100.0 0.00 2.25 main [2] 0.97 1.27 1/1 loop [1] ----------------------------------------------- 0.76 0.00 49997387/49997387 loop [1] [3] 33.6 0.76 0.00 49997387 pong [3] ----------------------------------------------- 0.52 0.00 50002613/50002613 loop [1] [4] 23.1 0.52 0.00 50002613 ping [4] ----------------------------------------------- Index by function name
It is easier to write an incorrect program than understand a correct one.[skip ]
-- Alan Perlis
#includedouble s = 0; void ping( int x ) { if ( x >= 0) s+=x; else s-=x; } void loop( int x ) { while ( x-- ) ping(x); } int main( int argc, char* argv[] ) { loop( atoi( argv[1] ) ); return 0; }
$ gcc -Wall -fprofile-arcs -ftest-coverage prog.c $ ./a.out 13 $ gcov prog.c $File 'prog.c' $Lignes exécutées: 90.91% de 11 $prog.c:creating 'prog.c.gcov'Haha, des lignes de codes n'ont pas été utilisées, le rapport complet est dans prog.c.gcov :
$ grep '####' prog.c.gcov $ #####: 5: else s-=x;la ligne 5 n'a jamais été exécutée.
Oh I've had the time of my life[skip ] Un programme qui sollicite le tas par des allocations dynamiques (malloc, calloc) doit impérativement restituer les blocs mémoires avant qu'ils ne deviennent inaccessibles ( free ). Dans le cas contraire, on parle de fuites mémoires. Imaginons un programme utilisant la fonction :
And I've never felt this way before
And I swear this is true and I owe it all to you
You-you-you .....
-- Dirty bit, dirty bit
int leak( int n ) { int *x, *y; x = calloc( n, sizeof(int) ); y = calloc( n, sizeof(int) ); free( y ); }la fonction fuit par x et à chaque appel de leak( n ) le processus perd 4*n octets ! Pour exécuter le programme avec le detecteur de fuites valgrind, il suffit de compiler avec l'option de debugage avant de lancer :
valgrind --leak-check=full ./a.out
==2415== Memcheck, a memory error detector. ==2415== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al. ==2415== Using LibVEX rev 1658, a library for dynamic binary translation. ==2415== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP. ==2415== Using valgrind-3.2.1, a dynamic binary instrumentation framework. ==2415== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al. ==2415== For more details, rerun with: -v ==2415== ==2415== ==2415== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 1) ==2415== malloc/free: in use at exit: 20 bytes in 1 blocks. ==2415== malloc/free: 2 allocs, 1 frees, 40 bytes allocated. ==2415== For counts of detected errors, rerun with: -v ==2415== searching for pointers to 1 not-freed blocks. ==2415== checked 47,228 bytes. ==2415== ==2415== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==2415== at 0x400473F: calloc (vg_replace_malloc.c:279) ==2415== by 0x804839C: leak (prog.c:5) ==2415== by 0x80483DF: main (prog.c:12)Et voila le travail, valgrind a détecté la perte de 20 octets ( en ligne 5 !) lors de l'appel de leak dans la fonction main.
==2415== ==2415== LEAK SUMMARY: ==2415== definitely lost: 20 bytes in 1 blocks. ==2415== possibly lost: 0 bytes in 0 blocks. ==2415== still reachable: 0 bytes in 0 blocks. ==2415== suppressed: 0 bytes in 0 blocks. ==2415== Reachable blocks (those to which a pointer was found) are not shown. ==2415== To see them, rerun with: --show-reachable=yes
The use of a program to prove the 4-color theorem will not change mathematics - it merely demonstrates that the theorem, a challenge for a century, is probably not important to mathematics.[skip ]
-- Alan Persis
#include "x.h" int main( void ) { hello(); return 0; }Le programme utilise un fichier de définition x.h et une fonction hello. On compile cette source en indiquant : un répertoire pour les fichiers d'entêtes, le chemin des bibliothèques et la bibliothèque à utiliser
$ gcc -Wall -Imyinc -Lmylib y.c -ltest $ ./a.out hello at 0x80483d8Nous sommes partis des x files :
$ cat ~/myinc/x.h #ifndef x_H #define x_H void hello( void ); #endif
$ cat x.c #includePour créer la bibliothèque statique avec la commande ar à partir :void hello( void ) { printf("hello at %p\n", hello); }
$ gcc -c x.c $ ar cr libtest.a x.o $ nm libtest.a x.o: 00000000 T hello U printf $ mv libtest.a ~/mylib/.
$ gcc -o x.so -shared x.c $ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $ gcc -Wall -Iinclude x.so y.cNotons que gcc s'est chargé de tout !
[pl@localhost ~]$ ./a.out hello at 0x487430Nous discuterons plus tard de cette adresse ! Maintenant, nous changeons la source x.c et nous recompilons uniquement la librairie dynamique
$ vi x.c $ grep @ x.c printf("hello @ %p\n", hello); $ gcc -o x.so -shared x.cEt donc, sans recompiler a.out :
[pl@localhost ~]$ ./a.out hello at 0x487430Ca c'est vraiment cool !
The kids seem to like them egg box... 360![skip ] Il est parfois utile de jeter un coup d'oeil au code assembleur produit par gcc. On peut le faire avec gdb , ou bien en compilant avec l'option -S. Par exemple, gcc -S prog.c produit un fichier prog.s. Donnons nous la source prog.c correspondant à la liste d'instructions :
-- The One Ronnie Show
int sq( int n ) { int r; while ( n-- ) s+ = n * n; }gcc -S prog.c produit le fichier prog.s :
.file "prog.c" .text .globl sq .type sq, @function sq: pushl %ebp ; entree dans un sous-programme. movl %esp, %ebp subl $4, %esp movl $0, -4(%ebp) .L2 : decl 8(%ebp) ; adresse du parametre n cmpl $-1, 8(%ebp) ; n-- ; hoho n est decremente avant le test ! jne .L4 jmp .L3 .L4: movl 8(%ebp), %eax ; on charge n dans l'accumulateur. movl %eax, %edx ; on passe dans edx imull 8(%ebp), %edx ; calcul du carre dans le registre edx leal -4(%ebp), %eax ; chargement de l'adresse de la variable locale s addl %edx, (%eax) ; on ajoute dx a la varaible locale s. jmp .L2 ; on boucle. .L3: movl -4(%ebp), %eax ; le resultat est passe par le registre eax leave ret .size sq, .-sq .section .note.GNU-stack,"",@progbits .ident "GCC: (GNU) 3.3.2 20031022 (Red Hat Linux 3.3.2-1)"
To iterate is human, to recurse divine.[skip ] Lors d'un appel de fonction les paramètres sont empilés de la droite vers la gauche. L'adresse de retour est empilée avant de faire un saut vers le sous-programme. A l'entrée du sous-programme le pointeur de base ebp est empilé, puis modifié de sorte à pointer sur un stack-frame dont la structure est :
-- L. Peter Deutsch
exemple |
||||
adresse asm |
contenu |
|
variable c> | adresse réelle> |
%esp | haut du stack frame | |||
variables locales |
z |
0xbfee5f80 | ||
-T (%ebp)> | premiere variable locale de
taille T |
i |
0xbfee5f84 | |
(%ebp) | %ebp de l'appelant | 0xbfee5fa8 | 0xbfee5f88 | |
4 (%ebp) | adresse de retour | 0x8048494 | ||
8 (%ebp) | premier parametre |
x |
0xbfee5f90 | |
... | parametre |
y |
0xbfee5f94 | |
|
z |
0xbfee5f98 | ||
resultat |
void proc( int x, int y, int z) { int i; int t[1]; t[0] = 13; printf("\n&i : %p", &i); printf("\n&t : %p", &t); printf("\n&p : %p", proc); printf("\n&x : %p", &x); printf("\n&y : %p", y); printf("\n&z : %p", z); for( i = 0 ; 7-i ; i++) printf("\nz[%d] = %10p", i, t[i] ); printf("\n"); }
int proc( int x, int y, int z) { int r; r = x+y+z; return r; } int main( void ) { int x; x = proc(1,2,3); return 0; }La source assembleur :
.file "pile.c" .text .globl proc .type proc, @function proc: pushl %ebp ;sauvegarde de bp movl %esp, %ebp ;configuration du stack-frame subl $4, %esp ;allocation des variable locale movl 12(%ebp), %eax ;second parametre vers ax addl 8(%ebp), %eax ;ajout du premier parametre addl 16(%ebp), %eax ;ajout du troisieme movl %eax, -4(%ebp) ;affectation du resultat dans la variable locale r movl -4(%ebp), %eax ;transmission du resultat par un registre leave ret ;depilage de l'adresse de retour .size proc, .-proc .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax subl %eax, %esp subl $4, %esp ; reservation de 4 octets sur la pile. pushl $3 ; empilage du dernier parametre pushl $2 ; empilage du second pushl $1 ; et du premier call proc ; appel a proc addl $16, %esp ; suppression des parametres movl %eax, -4(%ebp) ; récupération du résultat movl $0, %eax leave ret .size main, .-main .section .note.GNU-stack,"",@progbits .ident < "GCC: (GNU) 3.3.2 20031022 (Red Hat Linux 3.3.2-1)"
There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies.[skip ] Il est parfois utile d'inclure du code machine directement dans la source C. On utilise la directive, __asm__ . Dans l'exemple qui suit, int fastham(int z) calcule poids de Hamming de l'entier z.
-- C.A.R. Hoare
int fastham(int z) { __asm__(" xorl %eax, %eax"); __asm__(" movl 8(%ebp), %ecx"); __asm__("boucle: "); __asm__(" jecxz fin "); __asm__(" incl %eax "); __asm__(" movl %ecx, %ebx"); __asm__(" decl %ebx "); __asm__(" andl %ebx, %ecx"); __asm__(" jmp boucle "); __asm__("fin:"); __asm__(" leave");; __asm__(" ret"); }Notons bien que cà c'était avant...
#define fastham( z) ( __builtin_popcountll( z ) )